From 2b800619d38a5ae8856e33ee086aeff1030a6eea Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 17 Jun 2020 15:14:23 +0200 Subject: [PATCH 001/200] Added new constructors for Ref and ReadOnlyRef --- Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs | 10 ++++++++++ Microsoft.Toolkit.HighPerformance/Ref{T}.cs | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs b/Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs index c7156dbc63b..1945e1dffde 100644 --- a/Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs @@ -36,6 +36,16 @@ public ReadOnlyRef(in T value) Span = MemoryMarshal.CreateReadOnlySpan(ref r0, 1); } + /// + /// Initializes a new instance of the struct. + /// + /// The pointer to the target value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe ReadOnlyRef(void* pointer) + : this(Unsafe.AsRef(pointer)) + { + } + /// /// Gets the readonly reference represented by the current instance. /// diff --git a/Microsoft.Toolkit.HighPerformance/Ref{T}.cs b/Microsoft.Toolkit.HighPerformance/Ref{T}.cs index cc642ea48dc..e821a365969 100644 --- a/Microsoft.Toolkit.HighPerformance/Ref{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Ref{T}.cs @@ -34,6 +34,16 @@ public Ref(ref T value) Span = MemoryMarshal.CreateSpan(ref value, 1); } + /// + /// Initializes a new instance of the struct. + /// + /// The pointer to the target value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe Ref(void* pointer) + : this(ref Unsafe.AsRef(pointer)) + { + } + /// /// Gets the reference represented by the current instance. /// From b89a83cc336b83d384d3a86a48b33787b7b16196 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 17 Jun 2020 15:14:33 +0200 Subject: [PATCH 002/200] Added initial version of the Span2D type --- .../Memory/Span2D{T}.cs | 671 ++++++++++++++++++ 1 file changed, 671 insertions(+) create mode 100644 Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs new file mode 100644 index 00000000000..371c3ebb81b --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -0,0 +1,671 @@ +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.Toolkit.HighPerformance.Extensions; + +namespace Microsoft.Toolkit.HighPerformance.Memory +{ + /// + /// represents a 2D region of arbitrary memory. Like the type, + /// it can point to either managed or native memory, or to memory allocated on the stack. It is type- and memory-safe. + /// One key difference with and arrays is that the underlying buffer for a + /// instance might not be contiguous in memory: this is supported to enable mapping arbitrary 2D regions even if they + /// require padding between boundaries of sequential rows. All this logic is handled internally by the + /// type and it is transparent to the user, but note that working over discontiguous buffers has a performance impact. + /// + /// The type of items in the current instance. + public readonly ref struct Span2D + { + // Let's consider a representation of a discontiguous 2D memory + // region within an existing array. The data is represented in + // row-major order as usual, and the 'XX' grid cells represent + // locations that are mapped by a given Span2D instance: + // + // reference__ _________width_________ ________... + // \/ \/ + // | -- | -- | |- | -- | -- | -- | -- | -- | -- | -- | + // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- |_ + // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | | + // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |_height + // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- |_| + // | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | + // | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | + // ...__pitch__/ + // + // The pitch is used to calculate the offset between each + // discontiguous row, so that any arbitrary memory locations + // can be used to internally represent a 2D span. This gives + // users much more flexibility when creating spans from data. + + /// + /// The instance pointing to the first item in the target memory area. + /// + private readonly Ref reference; + + /// + /// The height of the specified 2D region. + /// + private readonly int height; + + /// + /// The width of the specified 2D region. + /// + private readonly int width; + + /// + /// The pitch of the specified 2D region. + /// + private readonly int pitch; + + /// + /// Initializes a new instance of the struct with the specified parameters. + /// + /// The reference to the first item to map. + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map (the distance between each row). + /// + /// Thrown when either , or are negative. + /// + public Span2D(ref T value, int height, int width, int pitch) + { + if ((height | width | pitch) < 0) + { + ThrowArgumentExceptionForNegativeSize(); + } + + this.reference = new Ref(ref value); + this.height = height; + this.width = width; + this.pitch = pitch; + } + + /// + /// Initializes a new instance of the struct with the specified parameters. + /// + /// The pointer to the first item to map. + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map (the distance between each row). + /// + /// Thrown when is a reference type or contains references. Also thrown + /// when either , or are negative. + /// + public unsafe Span2D(void* pointer, int height, int width, int pitch) + { + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + ThrowArgumentExceptionForManagedType(); + } + + if ((height | width | pitch) < 0) + { + ThrowArgumentExceptionForNegativeSize(); + } + + this.reference = new Ref(ref Unsafe.AsRef(pointer)); + this.height = height; + this.width = width; + this.pitch = pitch; + } + + /// + /// Initializes a new instance of the struct wrapping a 2D array. + /// + /// The given 2D array to wrap. + /// + /// Thrown when doesn't match . + /// + public Span2D(T[,]? array) + { + if (array is null) + { + this = default; + + return; + } + + if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) + { + ThrowArrayTypeMismatchException(); + } + + this.reference = new Ref(ref array[0, 0]); + this.height = array.GetLength(0); + this.width = array.GetLength(1); + this.pitch = 0; + } + + /// + /// Initializes a new instance of the struct wrapping a 2D array. + /// + /// The given 2D array to wrap. + /// The target row to map within . + /// The target column to map within . + /// The width to map within . + /// The height to map within . + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , or + /// are negative or not within the bounds that are valid for . + /// + public Span2D(T[,] array, int row, int column, int width, int height) + { + if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) + { + ThrowArrayTypeMismatchException(); + } + + int + rows = array.GetLength(0), + columns = array.GetLength(1); + + if ((uint)row >= (uint)rows || + (uint)column >= (uint)columns || + width > (columns - column) || + height > (rows - row)) + { + ThrowArgumentExceptionForNegativeOrInvalidParameter(); + } + + this.reference = new Ref(ref array[row, column]); + this.height = height; + this.width = width; + this.pitch = row + (array.GetLength(1) - column); + } + + /// + /// Gets an empty instance. + /// + public static Span Empty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => default; + } + + /// + /// Gets a value indicating whether the current instance is empty. + /// + public bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (this.height | this.width) == 0; + } + + /// + /// Gets the element at the specified zero-based indices. + /// + /// The target row to get the element from. + /// The target column to get the element from. + /// A reference to the element at the specified indices. + /// + /// Thrown when either or are invalid. + /// + public ref T this[int i, int j] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((uint)i >= (uint)this.height || + (uint)j >= (uint)this.width) + { + ThrowIndexOutOfRangeException(); + } + + return ref Unsafe.Add( + ref this.reference.Value, + (i * (this.width + this.pitch)) + j); + } + } + + /// + /// Gets the length of the current instance. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.height * this.width; + } + + /// + /// Clears the contents of the current instance. + /// + public void Clear() + { + if (this.pitch == 0) + { + // If the pitch is 0, it means all the target area is contiguous + // in memory with no padding between row boundaries. In this case + // we can just create a Span over the area and use it to clear it. + MemoryMarshal.CreateSpan(ref this.reference.Value, Length).Clear(); + } + else + { + ref T r0 = ref this.reference.Value; + IntPtr step = (IntPtr)(this.width + this.pitch); + + // Clear each row individually, as they're not contiguous + for (int i = 0; i < this.height; i++) + { + MemoryMarshal.CreateSpan(ref r0, this.width).Clear(); + + r0 = ref Unsafe.Add(ref r0, step); + } + } + } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(Span destination) + { + if (this.pitch == 0) + { + // If the pitch is 0, we can copy in a single pass + MemoryMarshal.CreateSpan(ref this.reference.Value, Length).CopyTo(destination); + } + else + { + if (Length > destination.Length) + { + ThrowArgumentExceptionForDestinationTooShort(); + } + + ref T sourceRef = ref this.reference.Value; + IntPtr step = (IntPtr)(this.width + this.pitch); + int offset = 0; + + // Copy each row individually + for (int i = 0; i < this.height; i++) + { + MemoryMarshal.CreateSpan(ref sourceRef, this.width).CopyTo(destination.Slice(offset)); + + sourceRef = ref Unsafe.Add(ref sourceRef, step); + offset += this.width; + } + } + } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(Span2D destination) + { + throw new NotImplementedException("TODO"); + } + +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Equals() on Span will always throw an exception. Use == instead.")] + public override bool Equals(object obj) + { + throw new NotSupportedException("Microsoft.Toolkit.HighPerformance.Span2D.Equals(object) is not supported"); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("GetHashCode() on Span will always throw an exception.")] + public override int GetHashCode() + { + throw new NotSupportedException("Microsoft.Toolkit.HighPerformance.Span2D.GetHashCode() is not supported"); + } +#pragma warning restore CS0809 + + /// + /// Fills the elements of this span with a specified value. + /// + /// The value to assign to each element of the instance. + public void Fill(T value) + { + if (this.pitch == 0) + { + MemoryMarshal.CreateSpan(ref this.reference.Value, Length).Fill(value); + } + else + { + ref T sourceRef = ref this.reference.Value; + IntPtr step = (IntPtr)(this.width + this.pitch); + + // Fill each row individually + for (int i = 0; i < this.height; i++) + { + MemoryMarshal.CreateSpan(ref sourceRef, this.width).Fill(value); + + sourceRef = ref Unsafe.Add(ref sourceRef, step); + } + } + } + + /// + /// Returns an enumerator for the current instance. + /// + /// + /// An enumerator that can be used to traverse the items in the current instance + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new Enumerator(this); + + /// + /// Returns a reference to the 0th element of the instance. If the current + /// instance is empty, returns a reference. It can be used for pinning + /// and is required to support the use of span within a fixed statement. + /// + /// A reference to the 0th element, or a reference. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [EditorBrowsable(EditorBrowsableState.Never)] + public unsafe ref T GetPinnableReference() + { + ref T r0 = ref Unsafe.AsRef(null); + + if (this.Length != 0) + { + r0 = ref this.reference.Value; + } + + return ref r0; + } + + /// + /// Returns a reference to the first element within the current instance, with no bounds check. + /// + /// A reference to the first element within the current instance. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T DangerousGetReference() + { + return ref this.reference.Value; + } + + /// + /// Checks whether two instances are equal. + /// + /// The first instance to compare. + /// The second instance to compare. + /// Whether or not and are equal. + public static bool operator ==(Span2D left, Span2D right) + { + return + Unsafe.AreSame(ref left.reference.Value, ref right.reference.Value) && + left.height == right.height && + left.width == right.width && + left.pitch == right.pitch; + } + + /// + /// Checks whether two instances are not equal. + /// + /// The first instance to compare. + /// The second instance to compare. + /// Whether or not and are not equal. + public static bool operator !=(Span2D left, Span2D right) + { + return !(left == right); + } + + /// + /// Implicily converts a given 2D array into a instance. + /// + /// The input 2D array to convert. + public static implicit operator Span2D(T[,]? array) => new Span2D(array); + + /// + /// Slices the current instance with the specified parameters. + /// + /// The target row to map within the current instance. + /// The target column to map within the current instance. + /// The width to map within the current instance. + /// The height to map within the current instance. + /// + /// Thrown when either , or + /// are negative or not within the bounds that are valid for the current instance. + /// + /// A new instance representing a slice of the current one. + [Pure] + public Span2D Slice(int row, int column, int width, int height) + { + throw new NotImplementedException("TODO"); + } + + /// + /// Copies the contents of the current instance into a new 2D array. + /// + /// A 2D array containing the data in the current instance. + [Pure] + public T[,] ToArray() + { + T[,] array = new T[this.height, this.width]; + + if (this.pitch == 0) + { + CopyTo(array.AsSpan()); + } + else + { + ref T sourceRef = ref this.reference.Value; + IntPtr step = (IntPtr)(this.width + this.pitch); + int offset = 0; + + // Copy each row individually + for (int i = 0; i < this.height; i++) + { + MemoryMarshal.CreateSpan(ref sourceRef, this.width).CopyTo(array.AsSpan().Slice(offset)); + + sourceRef = ref Unsafe.Add(ref sourceRef, step); + offset += this.width; + } + } + + return array; + } + + /// + public override string ToString() + { + return $"Microsoft.Toolkit.HighPerformance.Memory.Span2D<{typeof(T)}>[{this.height}, {this.width}]"; + } + + /// + /// Attempts to copy the current instance to a destination . + /// The target of the copy operation. + /// Whether or not the operaation was successful. + public bool TryCopyTo(Span destination) + { + if (destination.Length >= Length) + { + CopyTo(destination); + + return true; + } + + return false; + } + + /// + /// Tries to get a instance, if the underlying buffer is contiguous. + /// + /// The resulting , in case of success. + /// Whether or not was correctly assigned. + public bool TryAsSpan(out Span span) + { + if (this.pitch == 0) + { + // We can only create a Span if the buffer is contiguous + span = MemoryMarshal.CreateSpan(ref this.reference.Value, Length); + + return true; + } + + span = default; + + return false; + } + + /// + /// Provides an enumerator for the elements of a instance. + /// + public ref struct Enumerator + { + /// + /// The instance pointing to the first item in the target memory area. + /// + private readonly Ref reference; + + /// + /// The height of the specified 2D region. + /// + private readonly int height; + + /// + /// The width of the specified 2D region. + /// + private readonly int width; + + /// + /// The pitch of the specified 2D region. + /// + private readonly int pitch; + + /// + /// The current horizontal offset. + /// + private int x; + + /// + /// The current vertical offset. + /// + private int y; + + /// + /// Initializes a new instance of the struct. + /// + /// The target instance to enumerate. + internal Enumerator(Span2D span) + { + this.reference = span.reference; + this.height = span.height; + this.width = span.width; + this.pitch = span.pitch; + this.x = -1; + this.y = 0; + } + + /// + /// Implements the duck-typed method. + /// + /// whether a new element is available, otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + int x = this.x + 1; + + // Horizontal move, within range + if (x < this.width) + { + this.x = x; + + return true; + } + + // We reached the end of a row and there is at least + // another row available: wrap to a new line and continue. + if (this.y < (this.height - 1)) + { + this.x = 0; + this.y++; + + return true; + } + + return false; + } + + /// + /// Gets the duck-typed property. + /// + public ref T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return ref Unsafe.Add( + ref this.reference.Value, + (this.y * (this.width + this.pitch)) + this.x); + } + } + } + + /// + /// Throws an when the a given coordinate is invalid. + /// + /// + /// Throwing is technically discouraged in the docs, but + /// we're doing that here for consistency with the official type from the BCL. + /// + [DoesNotReturn] + private static void ThrowIndexOutOfRangeException() + { + throw new IndexOutOfRangeException(); + } + + /// + /// Throws an when using the * constructor with a managed type. + /// + [DoesNotReturn] + private static void ThrowArgumentExceptionForManagedType() + { + throw new ArgumentException("Can't create a Span2D from a pointer when T is a managed type"); + } + + /// + /// Throws an when using an array of an invalid type. + /// + [DoesNotReturn] + private static void ThrowArrayTypeMismatchException() + { + throw new ArrayTypeMismatchException("The given array doesn't match the specified Span2D type"); + } + + /// + /// Throws an when a constructor parameter is negative. + /// + [DoesNotReturn] + private static void ThrowArgumentExceptionForNegativeSize() + { + throw new ArgumentException("The size parameters must be positive values"); + } + + /// + /// Throws an when a constructor parameter is negative or invalid. + /// + [DoesNotReturn] + private static void ThrowArgumentExceptionForNegativeOrInvalidParameter() + { + throw new ArgumentException("The given parameters must be non negative and within valid range for the array"); + } + + /// + /// Throws an when the target span is too short. + /// + [DoesNotReturn] + private static void ThrowArgumentExceptionForDestinationTooShort() + { + throw new ArgumentException("The target span is too short to copy all the current items to"); + } + } +} From d6980bce55e287243ea9d63034bbc4e5564fe4d6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 17 Jun 2020 16:25:11 +0200 Subject: [PATCH 003/200] Minor code tweaks to Span2D --- .../Memory/Span2D{T}.cs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 371c3ebb81b..d6fb6a6040e 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -1,6 +1,11 @@ -using System; +// 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. + +#if SPAN_RUNTIME_SUPPORT + +using System; using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -182,11 +187,7 @@ public Span2D(T[,] array, int row, int column, int width, int height) /// /// Gets an empty instance. /// - public static Span Empty - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => default; - } + public static Span Empty => default; /// /// Gets a value indicating whether the current instance is empty. @@ -617,7 +618,6 @@ public ref T Current /// Throwing is technically discouraged in the docs, but /// we're doing that here for consistency with the official type from the BCL. /// - [DoesNotReturn] private static void ThrowIndexOutOfRangeException() { throw new IndexOutOfRangeException(); @@ -626,7 +626,6 @@ private static void ThrowIndexOutOfRangeException() /// /// Throws an when using the * constructor with a managed type. /// - [DoesNotReturn] private static void ThrowArgumentExceptionForManagedType() { throw new ArgumentException("Can't create a Span2D from a pointer when T is a managed type"); @@ -635,7 +634,6 @@ private static void ThrowArgumentExceptionForManagedType() /// /// Throws an when using an array of an invalid type. /// - [DoesNotReturn] private static void ThrowArrayTypeMismatchException() { throw new ArrayTypeMismatchException("The given array doesn't match the specified Span2D type"); @@ -644,7 +642,6 @@ private static void ThrowArrayTypeMismatchException() /// /// Throws an when a constructor parameter is negative. /// - [DoesNotReturn] private static void ThrowArgumentExceptionForNegativeSize() { throw new ArgumentException("The size parameters must be positive values"); @@ -653,7 +650,6 @@ private static void ThrowArgumentExceptionForNegativeSize() /// /// Throws an when a constructor parameter is negative or invalid. /// - [DoesNotReturn] private static void ThrowArgumentExceptionForNegativeOrInvalidParameter() { throw new ArgumentException("The given parameters must be non negative and within valid range for the array"); @@ -662,10 +658,11 @@ private static void ThrowArgumentExceptionForNegativeOrInvalidParameter() /// /// Throws an when the target span is too short. /// - [DoesNotReturn] private static void ThrowArgumentExceptionForDestinationTooShort() { throw new ArgumentException("The target span is too short to copy all the current items to"); } } } + +#endif From eb1bb6e0974bca10d1a93bef963f0024b20139c0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 17 Jun 2020 16:51:03 +0200 Subject: [PATCH 004/200] Added initial version of the Memory2D type --- .../Memory/Memory2D{T}.cs | 234 ++++++++++++++++++ .../Memory/Span2D{T}.cs | 3 +- 2 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs new file mode 100644 index 00000000000..d9fea85db5b --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -0,0 +1,234 @@ +using System; +using System.Buffers; +using System.ComponentModel; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.Toolkit.HighPerformance.Extensions; + +namespace Microsoft.Toolkit.HighPerformance.Memory +{ + /// + /// represents a 2D region of arbitrary memory. It is to + /// what is to . For further details on how the internal layout + /// is structured, see the docs for . The type can wrap arrays + /// of any rank, provided that a valid series of parameters for the target memory area(s) are provided. + /// + /// The type of items in the current instance. + public readonly struct Memory2D : IEquatable> + { + /// + /// The target instance, if present. + /// + private readonly Array? array; + + /// + /// The initial offset within . + /// + private readonly IntPtr offset; + + /// + /// The height of the specified 2D region. + /// + private readonly int height; + + /// + /// The width of the specified 2D region. + /// + private readonly int width; + + /// + /// The pitch of the specified 2D region. + /// + private readonly int pitch; + + /// + /// Initializes a new instance of the struct. + /// + /// The target array to wrap. + /// + /// Thrown when doesn't match . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory2D(T[,]? array) + { + if (array is null) + { + this = default; + + return; + } + + if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) + { + ThrowArrayTypeMismatchException(); + } + + this.array = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReference()); + this.height = array.GetLength(0); + this.width = array.GetLength(1); + this.pitch = 0; + } + + /// + /// Defines an implicit conversion of an array to a + /// + public static implicit operator Memory2D(T[,]? array) => new Memory2D(array); + + /// + /// Gets an empty instance. + /// + public static Memory2D Empty => default; + + /// + /// Gets a value indicating whether the current instance is empty. + /// + public bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (this.height | this.width) == 0; + } + + /// + /// Gets the length of the current instance. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.height * this.width; + } + + /// + public override string ToString() + { + return $"Microsoft.Toolkit.HighPerformance.Memory.Memory2D<{typeof(T)}>[{this.height}, {this.width}]"; + } + + /// + /// Slices the current instance with the specified parameters. + /// + /// The target row to map within the current instance. + /// The target column to map within the current instance. + /// The width to map within the current instance. + /// The height to map within the current instance. + /// + /// Thrown when either , or + /// are negative or not within the bounds that are valid for the current instance. + /// + /// A new instance representing a slice of the current one. + [Pure] + public Memory2D Slice(int row, int column, int width, int height) + { + throw new NotImplementedException("TODO"); + } + + /// + /// Gets a instance from the current memory. + /// + public Span2D Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (!(this.array is null)) + { + ref T r0 = ref this.array.DangerousGetObjectDataReferenceAt(this.offset); + + return new Span2D(ref r0, this.height, this.width, this.pitch); + } + + return default; + } + } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(Memory destination) => Span.CopyTo(destination.Span); + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operaation was successful. + public bool TryCopyTo(Memory destination) => Span.TryCopyTo(destination.Span); + + /// + /// Creates a handle for the memory. + /// The GC will not move the memory until the returned + /// is disposed, enabling taking and using the memory's address. + /// + /// + /// An instance with nonprimitive (non-blittable) members cannot be pinned. + /// + /// A instance wrapping the pinned handle. + public unsafe MemoryHandle Pin() + { + if (!(this.array is null)) + { + GCHandle handle = GCHandle.Alloc(this.array, GCHandleType.Pinned); + + void* pointer = Unsafe.AsPointer(ref this.array.DangerousGetObjectDataReferenceAt(this.offset)); + + return new MemoryHandle(pointer, handle); + } + + return default; + } + + /// + /// Copies the contents of the current instance into a new 2D array. + /// + /// A 2D array containing the data in the current instance. + [Pure] + public T[,] ToArray() => Span.ToArray(); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) + { + return obj is Memory2D memory && Equals(memory); + } + + /// + public bool Equals(Memory2D other) + { + return + this.array == other.array && + this.offset == other.offset && + this.height == other.height && + this.width == other.width && + this.pitch == other.pitch; + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + if (!(this.array is null)) + { + return HashCode.Combine( + RuntimeHelpers.GetHashCode(this.array), + this.offset, + this.height, + this.width, + this.pitch); + } + + return 0; + } + + /// + /// Throws an when using an array of an invalid type. + /// + private static void ThrowArrayTypeMismatchException() + { + throw new ArrayTypeMismatchException("The given array doesn't match the specified Span2D type"); + } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index d6fb6a6040e..11048775920 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -482,7 +482,8 @@ public override string ToString() } /// - /// Attempts to copy the current instance to a destination . + /// Attempts to copy the current instance to a destination . + /// /// The target of the copy operation. /// Whether or not the operaation was successful. public bool TryCopyTo(Span destination) From f11b77dc2c1e10388b27a367aca67d43f2462152 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 17 Jun 2020 16:57:34 +0200 Subject: [PATCH 005/200] Added missing Memory2D header --- Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index d9fea85db5b..c2bcd77de7d 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -1,4 +1,8 @@ -using System; +// 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.Buffers; using System.ComponentModel; using System.Diagnostics.Contracts; From 64a293c7393610c6bfd7132e19e1beec5b7fbc11 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 17 Jun 2020 17:55:25 +0200 Subject: [PATCH 006/200] Improved Span2D layout (removed 4 bytes) --- .../Memory/Span2D{T}.cs | 85 ++++++++----------- 1 file changed, 37 insertions(+), 48 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 11048775920..e96e98082c0 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -46,14 +46,13 @@ public readonly ref struct Span2D // users much more flexibility when creating spans from data. /// - /// The instance pointing to the first item in the target memory area. + /// The instance pointing to the first item in the target memory area. /// - private readonly Ref reference; - - /// - /// The height of the specified 2D region. - /// - private readonly int height; + /// + /// The field maps to the height of the 2D region. + /// This is done to save 4 bytes in the layout of the type. + /// + private readonly Span span; /// /// The width of the specified 2D region. @@ -82,8 +81,7 @@ public Span2D(ref T value, int height, int width, int pitch) ThrowArgumentExceptionForNegativeSize(); } - this.reference = new Ref(ref value); - this.height = height; + this.span = MemoryMarshal.CreateSpan(ref value, height); this.width = width; this.pitch = pitch; } @@ -111,8 +109,7 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) ThrowArgumentExceptionForNegativeSize(); } - this.reference = new Ref(ref Unsafe.AsRef(pointer)); - this.height = height; + this.span = new Span(pointer, height); this.width = width; this.pitch = pitch; } @@ -138,8 +135,7 @@ public Span2D(T[,]? array) ThrowArrayTypeMismatchException(); } - this.reference = new Ref(ref array[0, 0]); - this.height = array.GetLength(0); + this.span = MemoryMarshal.CreateSpan(ref array[0, 0], array.GetLength(0)); this.width = array.GetLength(1); this.pitch = 0; } @@ -178,8 +174,7 @@ public Span2D(T[,] array, int row, int column, int width, int height) ThrowArgumentExceptionForNegativeOrInvalidParameter(); } - this.reference = new Ref(ref array[row, column]); - this.height = height; + this.span = MemoryMarshal.CreateSpan(ref array[row, column], height); this.width = width; this.pitch = row + (array.GetLength(1) - column); } @@ -195,7 +190,7 @@ public Span2D(T[,] array, int row, int column, int width, int height) public bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (this.height | this.width) == 0; + get => (this.span.Length | this.width) == 0; } /// @@ -212,14 +207,14 @@ public bool IsEmpty [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((uint)i >= (uint)this.height || + if ((uint)i >= (uint)this.span.Length || (uint)j >= (uint)this.width) { ThrowIndexOutOfRangeException(); } return ref Unsafe.Add( - ref this.reference.Value, + ref MemoryMarshal.GetReference(this.span), (i * (this.width + this.pitch)) + j); } } @@ -230,7 +225,7 @@ public bool IsEmpty public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.height * this.width; + get => this.span.Length * this.width; } /// @@ -243,15 +238,15 @@ public void Clear() // If the pitch is 0, it means all the target area is contiguous // in memory with no padding between row boundaries. In this case // we can just create a Span over the area and use it to clear it. - MemoryMarshal.CreateSpan(ref this.reference.Value, Length).Clear(); + MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Length).Clear(); } else { - ref T r0 = ref this.reference.Value; + ref T r0 = ref MemoryMarshal.GetReference(this.span); IntPtr step = (IntPtr)(this.width + this.pitch); // Clear each row individually, as they're not contiguous - for (int i = 0; i < this.height; i++) + for (int i = 0; i < this.span.Length; i++) { MemoryMarshal.CreateSpan(ref r0, this.width).Clear(); @@ -272,7 +267,7 @@ public void CopyTo(Span destination) if (this.pitch == 0) { // If the pitch is 0, we can copy in a single pass - MemoryMarshal.CreateSpan(ref this.reference.Value, Length).CopyTo(destination); + MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Length).CopyTo(destination); } else { @@ -281,12 +276,12 @@ public void CopyTo(Span destination) ThrowArgumentExceptionForDestinationTooShort(); } - ref T sourceRef = ref this.reference.Value; + ref T sourceRef = ref MemoryMarshal.GetReference(this.span); IntPtr step = (IntPtr)(this.width + this.pitch); int offset = 0; // Copy each row individually - for (int i = 0; i < this.height; i++) + for (int i = 0; i < this.span.Length; i++) { MemoryMarshal.CreateSpan(ref sourceRef, this.width).CopyTo(destination.Slice(offset)); @@ -334,15 +329,15 @@ public void Fill(T value) { if (this.pitch == 0) { - MemoryMarshal.CreateSpan(ref this.reference.Value, Length).Fill(value); + MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Length).Fill(value); } else { - ref T sourceRef = ref this.reference.Value; + ref T sourceRef = ref MemoryMarshal.GetReference(this.span); IntPtr step = (IntPtr)(this.width + this.pitch); // Fill each row individually - for (int i = 0; i < this.height; i++) + for (int i = 0; i < this.span.Length; i++) { MemoryMarshal.CreateSpan(ref sourceRef, this.width).Fill(value); @@ -376,7 +371,7 @@ public unsafe ref T GetPinnableReference() if (this.Length != 0) { - r0 = ref this.reference.Value; + r0 = ref MemoryMarshal.GetReference(this.span); } return ref r0; @@ -390,7 +385,7 @@ public unsafe ref T GetPinnableReference() [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T DangerousGetReference() { - return ref this.reference.Value; + return ref MemoryMarshal.GetReference(this.span); } /// @@ -402,8 +397,7 @@ public ref T DangerousGetReference() public static bool operator ==(Span2D left, Span2D right) { return - Unsafe.AreSame(ref left.reference.Value, ref right.reference.Value) && - left.height == right.height && + left.span == right.span && left.width == right.width && left.pitch == right.pitch; } @@ -450,7 +444,7 @@ public Span2D Slice(int row, int column, int width, int height) [Pure] public T[,] ToArray() { - T[,] array = new T[this.height, this.width]; + T[,] array = new T[this.span.Length, this.width]; if (this.pitch == 0) { @@ -458,12 +452,12 @@ public Span2D Slice(int row, int column, int width, int height) } else { - ref T sourceRef = ref this.reference.Value; + ref T sourceRef = ref MemoryMarshal.GetReference(this.span); IntPtr step = (IntPtr)(this.width + this.pitch); int offset = 0; // Copy each row individually - for (int i = 0; i < this.height; i++) + for (int i = 0; i < this.span.Length; i++) { MemoryMarshal.CreateSpan(ref sourceRef, this.width).CopyTo(array.AsSpan().Slice(offset)); @@ -478,7 +472,7 @@ public Span2D Slice(int row, int column, int width, int height) /// public override string ToString() { - return $"Microsoft.Toolkit.HighPerformance.Memory.Span2D<{typeof(T)}>[{this.height}, {this.width}]"; + return $"Microsoft.Toolkit.HighPerformance.Memory.Span2D<{typeof(T)}>[{this.span.Length}, {this.width}]"; } /// @@ -508,7 +502,7 @@ public bool TryAsSpan(out Span span) if (this.pitch == 0) { // We can only create a Span if the buffer is contiguous - span = MemoryMarshal.CreateSpan(ref this.reference.Value, Length); + span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Length); return true; } @@ -524,14 +518,10 @@ public bool TryAsSpan(out Span span) public ref struct Enumerator { /// - /// The instance pointing to the first item in the target memory area. - /// - private readonly Ref reference; - - /// - /// The height of the specified 2D region. + /// The instance pointing to the first item in the target memory area. /// - private readonly int height; + /// Just like in , the length is the height of the 2D region. + private readonly Span span; /// /// The width of the specified 2D region. @@ -559,8 +549,7 @@ public ref struct Enumerator /// The target instance to enumerate. internal Enumerator(Span2D span) { - this.reference = span.reference; - this.height = span.height; + this.span = span.span; this.width = span.width; this.pitch = span.pitch; this.x = -1; @@ -586,7 +575,7 @@ public bool MoveNext() // We reached the end of a row and there is at least // another row available: wrap to a new line and continue. - if (this.y < (this.height - 1)) + if (this.y < (this.span.Length - 1)) { this.x = 0; this.y++; @@ -606,7 +595,7 @@ public ref T Current get { return ref Unsafe.Add( - ref this.reference.Value, + ref MemoryMarshal.GetReference(this.span), (this.y * (this.width + this.pitch)) + this.x); } } From 4de84df49ae0b5c51b427c81b7dd81310b143749 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 17 Jun 2020 22:52:28 +0200 Subject: [PATCH 007/200] Minor code refactoring --- .../Extensions/{ArrayExtensions.cs => ArrayExtensions.1D.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Microsoft.Toolkit.HighPerformance/Extensions/{ArrayExtensions.cs => ArrayExtensions.1D.cs} (100%) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs similarity index 100% rename from Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.cs rename to Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs From ad31cfe248be6ad76629baacd71d6a23834b7dd0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 17 Jun 2020 22:52:42 +0200 Subject: [PATCH 008/200] Fixed a bug in T[,].AsSpan() on .NET Standard 2.1 --- .../Extensions/ArrayExtensions.2D.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index a7a4baa075d..e4d2c9a2943 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -256,6 +256,12 @@ public static Span AsSpan(this T[,] array) ref T r0 = ref Unsafe.As(ref arrayData.Data); #else int length = array.Length; + + if (length == 0) + { + return default; + } + ref T r0 = ref array[0, 0]; #endif return MemoryMarshal.CreateSpan(ref r0, length); From 26e33db7c521e2c6097976a11b7725ccc51e0bef Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 17 Jun 2020 22:52:52 +0200 Subject: [PATCH 009/200] Added some T[,,] extensions --- .../Extensions/ArrayExtensions.3D.cs | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs new file mode 100644 index 00000000000..64cd592049f --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -0,0 +1,179 @@ +// 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; +#if SPAN_RUNTIME_SUPPORT +using System.Runtime.InteropServices; +#endif +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 3D array, with no bounds checks. + /// + /// The type of elements in the input 3D 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 + if (array.Length > 0) + { + return ref array[0, 0, 0]; + } + + unsafe + { + return ref Unsafe.AsRef(null); + } +#endif + } + + /// + /// Returns a reference to an element at a specified coordinate within a given 3D array, with no bounds checks. + /// + /// The type of elements in the input 3D array instance. + /// The input 2D array instance. + /// The depth index of the element to retrieve within . + /// 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, int k) + { +#if NETCORE_RUNTIME + var arrayData = Unsafe.As(array); + int offset = (i * arrayData.Height * arrayData.Width) + (j * arrayData.Width) + k; + 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) && + (uint)k < (uint)array.GetLength(2)) + { + return ref array[i, j, k]; + } + + unsafe + { + return ref Unsafe.AsRef(null); + } +#endif + } + +#if NETCORE_RUNTIME + // See description for this in the 2D partial file. + // Using the CHW naming scheme here (like with RGB images). + [StructLayout(LayoutKind.Sequential)] + private sealed class RawArray3DData + { +#pragma warning disable CS0649 // Unassigned fields +#pragma warning disable SA1401 // Fields should be private + public IntPtr Length; + public int Channel; + public int Height; + public int Width; + public int ChannelLowerBound; + public int HeightLowerBound; + public int WidthLowerBound; + public byte Data; +#pragma warning restore CS0649 +#pragma warning restore SA1401 + } +#endif + +#if SPAN_RUNTIME_SUPPORT + /// + /// Cretes a new over an input 3D array. + /// + /// The type of elements in the input 3D array instance. + /// The input 3D 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); + + // See comments for this in the 2D overload + int length = checked((int)Unsafe.As(ref arrayData.Length)); + ref T r0 = ref Unsafe.As(ref arrayData.Data); +#else + int length = array.Length; + + if (length == 0) + { + return default; + } + + ref T r0 = ref array[0, 0, 0]; +#endif + return MemoryMarshal.CreateSpan(ref r0, length); + } +#endif + + /// + /// Counts the number of occurrences of a given value into a target 3D array instance. + /// + /// The type of items in the input 3D array instance. + /// The input 3D 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 3D array instance using the Djb2 algorithm. + /// For more info, see the documentation for . + /// + /// The type of items in the input 3D array instance. + /// The input 3D array instance. + /// The Djb2 value for the input 3D 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); + } + } +} From 033055a397dd2b3101e32c212a7d9d6ca313a811 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 17 Jun 2020 22:54:02 +0200 Subject: [PATCH 010/200] Added more Memory2D constructors --- .../Memory/Memory2D{T}.cs | 135 ++++++++++++++++-- 1 file changed, 123 insertions(+), 12 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index c2bcd77de7d..cb9c047fd37 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -22,12 +22,12 @@ namespace Microsoft.Toolkit.HighPerformance.Memory public readonly struct Memory2D : IEquatable> { /// - /// The target instance, if present. + /// The target instance, if present. /// - private readonly Array? array; + private readonly object? instance; /// - /// The initial offset within . + /// The initial offset within . /// private readonly IntPtr offset; @@ -46,6 +46,46 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// private readonly int pitch; + /// + /// Initializes a new instance of the struct. + /// + /// The target array to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory2D(T[] array, int offset, int width, int height) + { + if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) + { + ThrowArrayTypeMismatchException(); + } + + if ((uint)offset >= (uint)array.Length) + { + throw new Exception(); + } + + int remaining = array.Length - offset; + + if (((uint)width * (uint)height) > (uint)remaining) + { + throw new Exception(); + } + + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)); + this.height = height; + this.width = width; + this.pitch = 0; + } + /// /// Initializes a new instance of the struct. /// @@ -68,13 +108,84 @@ public Memory2D(T[,]? array) ThrowArrayTypeMismatchException(); } - this.array = array; + this.instance = array; this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReference()); this.height = array.GetLength(0); this.width = array.GetLength(1); this.pitch = 0; } + /// + /// Initializes a new instance of the struct wrapping a 2D array. + /// + /// The given 2D array to wrap. + /// The target row to map within . + /// The target column to map within . + /// The width to map within . + /// The height to map within . + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , or + /// are negative or not within the bounds that are valid for . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory2D(T[,] array, int row, int column, int width, int height) + { + if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) + { + ThrowArrayTypeMismatchException(); + } + + int + rows = array.GetLength(0), + columns = array.GetLength(1); + + if ((uint)row >= (uint)rows || + (uint)column >= (uint)columns || + width > (columns - column) || + height > (rows - row)) + { + throw new Exception(); + } + + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column)); + this.height = height; + this.width = width; + this.pitch = row + (array.GetLength(1) - column); + } + + /// + /// Initializes a new instance of the struct wrapping a layer in a 3D array. + /// + /// The given 3D array to wrap. + /// The target layer to map within . + /// + /// Thrown when doesn't match . + /// + /// Thrown when either is invalid. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory2D(T[,,] array, int depth) + { + if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) + { + ThrowArrayTypeMismatchException(); + } + + if ((uint)depth >= (uint)array.GetLength(0)) + { + throw new Exception(); + } + + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0)); + this.height = array.GetLength(1); + this.width = array.GetLength(2); + this.pitch = 0; + } + /// /// Defines an implicit conversion of an array to a /// @@ -135,9 +246,9 @@ public Span2D Span [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if (!(this.array is null)) + if (!(this.instance is null)) { - ref T r0 = ref this.array.DangerousGetObjectDataReferenceAt(this.offset); + ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset); return new Span2D(ref r0, this.height, this.width, this.pitch); } @@ -173,11 +284,11 @@ public Span2D Span /// A instance wrapping the pinned handle. public unsafe MemoryHandle Pin() { - if (!(this.array is null)) + if (!(this.instance is null)) { - GCHandle handle = GCHandle.Alloc(this.array, GCHandleType.Pinned); + GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned); - void* pointer = Unsafe.AsPointer(ref this.array.DangerousGetObjectDataReferenceAt(this.offset)); + void* pointer = Unsafe.AsPointer(ref this.instance.DangerousGetObjectDataReferenceAt(this.offset)); return new MemoryHandle(pointer, handle); } @@ -203,7 +314,7 @@ public override bool Equals(object? obj) public bool Equals(Memory2D other) { return - this.array == other.array && + this.instance == other.instance && this.offset == other.offset && this.height == other.height && this.width == other.width && @@ -214,10 +325,10 @@ public bool Equals(Memory2D other) [EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() { - if (!(this.array is null)) + if (!(this.instance is null)) { return HashCode.Combine( - RuntimeHelpers.GetHashCode(this.array), + RuntimeHelpers.GetHashCode(this.instance), this.offset, this.height, this.width, From f0de8bb262c2ac67828b2483eb7edcef66a91d38 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 17 Jun 2020 23:05:02 +0200 Subject: [PATCH 011/200] Disabled Memory2D before .NET Standard 2.1 --- Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index cb9c047fd37..6d94a299e8c 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if SPAN_RUNTIME_SUPPORT + using System; using System.Buffers; using System.ComponentModel; @@ -347,3 +349,5 @@ private static void ThrowArrayTypeMismatchException() } } } + +#endif From fdd75e8b4cee56e28e52b9f3ba42ab4045c95295 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 17 Jun 2020 23:59:57 +0200 Subject: [PATCH 012/200] Enabled Span2D on all targets --- .../Memory/Span2D{T}.cs | 211 ++++++++++++++++-- 1 file changed, 193 insertions(+), 18 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index e96e98082c0..093b5909c23 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -2,11 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if SPAN_RUNTIME_SUPPORT - using System; using System.ComponentModel; using System.Diagnostics.Contracts; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Extensions; @@ -44,7 +43,7 @@ public readonly ref struct Span2D // discontiguous row, so that any arbitrary memory locations // can be used to internally represent a 2D span. This gives // users much more flexibility when creating spans from data. - +#if SPAN_RUNTIME_SUPPORT /// /// The instance pointing to the first item in the target memory area. /// @@ -53,6 +52,22 @@ public readonly ref struct Span2D /// This is done to save 4 bytes in the layout of the type. /// private readonly Span span; +#else + /// + /// The target instance, if present. + /// + private readonly object? instance; + + /// + /// The initial offset within . + /// + private readonly IntPtr offset; + + /// + /// The height of the specified 2D region. + /// + private readonly int height; +#endif /// /// The width of the specified 2D region. @@ -64,6 +79,7 @@ public readonly ref struct Span2D /// private readonly int pitch; +#if SPAN_RUNTIME_SUPPORT /// /// Initializes a new instance of the struct with the specified parameters. /// @@ -113,6 +129,7 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) this.width = width; this.pitch = pitch; } +#endif /// /// Initializes a new instance of the struct wrapping a 2D array. @@ -130,12 +147,27 @@ public Span2D(T[,]? array) return; } - if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) + if ( +#pragma warning disable SA1003 // Whitespace before ! operator +#if NETSTANDARD1_4 + + !typeof(T).GetTypeInfo().IsValueType && +#else + !typeof(T).IsValueType && +#endif +#pragma warning restore SA1003 + array.GetType() != typeof(T[])) { ThrowArrayTypeMismatchException(); } +#if SPAN_RUNTIME_SUPPORT this.span = MemoryMarshal.CreateSpan(ref array[0, 0], array.GetLength(0)); +#else + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(0, 0)); + this.height = array.GetLength(0); +#endif this.width = array.GetLength(1); this.pitch = 0; } @@ -157,7 +189,16 @@ public Span2D(T[,]? array) /// public Span2D(T[,] array, int row, int column, int width, int height) { - if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) + if ( +#pragma warning disable SA1003 // Whitespace before ! operator +#if NETSTANDARD1_4 + + !typeof(T).GetTypeInfo().IsValueType && +#else + !typeof(T).IsValueType && +#endif +#pragma warning restore SA1003 + array.GetType() != typeof(T[])) { ThrowArrayTypeMismatchException(); } @@ -174,7 +215,13 @@ public Span2D(T[,] array, int row, int column, int width, int height) ThrowArgumentExceptionForNegativeOrInvalidParameter(); } +#if SPAN_RUNTIME_SUPPORT this.span = MemoryMarshal.CreateSpan(ref array[row, column], height); +#else + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column)); + this.height = array.GetLength(0); +#endif this.width = width; this.pitch = row + (array.GetLength(1) - column); } @@ -190,7 +237,14 @@ public Span2D(T[,] array, int row, int column, int width, int height) public bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (this.span.Length | this.width) == 0; + get + { +#if SPAN_RUNTIME_SUPPORT + return (this.span.Length | this.width) == 0; +#else + return (this.height | this.width) == 0; +#endif + } } /// @@ -207,15 +261,27 @@ public bool IsEmpty [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((uint)i >= (uint)this.span.Length || + if ( +#pragma warning disable SA1003 // Whitespace before cast +#if SPAN_RUNTIME_SUPPORT + (uint)i >= (uint)this.span.Length || +#else + (uint)i >= (uint)this.height || +#endif +#pragma warning restore SA1003 (uint)j >= (uint)this.width) { ThrowIndexOutOfRangeException(); } - return ref Unsafe.Add( - ref MemoryMarshal.GetReference(this.span), - (i * (this.width + this.pitch)) + j); +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref MemoryMarshal.GetReference(this.span); +#else + ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); +#endif + int index = (i * (this.width + this.pitch)) + j; + + return ref Unsafe.Add(ref r0, index); } } @@ -225,7 +291,14 @@ ref MemoryMarshal.GetReference(this.span), public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.span.Length * this.width; + get + { +#if SPAN_RUNTIME_SUPPORT + return this.span.Length * this.width; +#else + return this.height * this.width; +#endif + } } /// @@ -233,6 +306,7 @@ public int Length /// public void Clear() { +#if SPAN_RUNTIME_SUPPORT if (this.pitch == 0) { // If the pitch is 0, it means all the target area is contiguous @@ -253,6 +327,13 @@ public void Clear() r0 = ref Unsafe.Add(ref r0, step); } } +#else + // Fallback to the enumerator to traverse the span + foreach (ref T item in this) + { + item = default!; + } +#endif } /// @@ -264,6 +345,7 @@ public void Clear() /// public void CopyTo(Span destination) { +#if SPAN_RUNTIME_SUPPORT if (this.pitch == 0) { // If the pitch is 0, we can copy in a single pass @@ -289,6 +371,18 @@ public void CopyTo(Span destination) offset += this.width; } } +#else + // Similar to the previous case + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + IntPtr offset = default; + + foreach (T item in this) + { + Unsafe.Add(ref destinationRef, offset) = item; + + offset += 1; + } +#endif } /// @@ -327,6 +421,7 @@ public override int GetHashCode() /// The value to assign to each element of the instance. public void Fill(T value) { +#if SPAN_RUNTIME_SUPPORT if (this.pitch == 0) { MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Length).Fill(value); @@ -344,6 +439,13 @@ public void Fill(T value) sourceRef = ref Unsafe.Add(ref sourceRef, step); } } +#else + // Use the enumerator again + foreach (ref T item in this) + { + item = value; + } +#endif } /// @@ -371,7 +473,11 @@ public unsafe ref T GetPinnableReference() if (this.Length != 0) { +#if SPAN_RUNTIME_SUPPORT r0 = ref MemoryMarshal.GetReference(this.span); +#else + r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); +#endif } return ref r0; @@ -385,7 +491,11 @@ public unsafe ref T GetPinnableReference() [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T DangerousGetReference() { +#if SPAN_RUNTIME_SUPPORT return ref MemoryMarshal.GetReference(this.span); +#else + return ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); +#endif } /// @@ -397,7 +507,13 @@ public ref T DangerousGetReference() public static bool operator ==(Span2D left, Span2D right) { return +#if SPAN_RUNTIME_SUPPORT left.span == right.span && +#else + ReferenceEquals(left.instance, right.instance) && + left.offset == right.offset && + left.height == right.height && +#endif left.width == right.width && left.pitch == right.pitch; } @@ -444,6 +560,7 @@ public Span2D Slice(int row, int column, int width, int height) [Pure] public T[,] ToArray() { +#if SPAN_RUNTIME_SUPPORT T[,] array = new T[this.span.Length, this.width]; if (this.pitch == 0) @@ -465,6 +582,24 @@ public Span2D Slice(int row, int column, int width, int height) offset += this.width; } } +#else + T[,] array = new T[this.height, this.width]; + + // Skip the initialization if the array is empty + if (Length > 0) + { + ref T r0 = ref array.DangerousGetReference(); + IntPtr offset = default; + + // Fallback once again on the enumerator to copy the items + foreach (T item in this) + { + Unsafe.Add(ref r0, offset) = item; + + offset += 1; + } + } +#endif return array; } @@ -472,7 +607,13 @@ public Span2D Slice(int row, int column, int width, int height) /// public override string ToString() { - return $"Microsoft.Toolkit.HighPerformance.Memory.Span2D<{typeof(T)}>[{this.span.Length}, {this.width}]"; +#if SPAN_RUNTIME_SUPPORT + int height = this.span.Length; +#else + int height = this.height; +#endif + + return $"Microsoft.Toolkit.HighPerformance.Memory.Span2D<{typeof(T)}>[{height}, {this.width}]"; } /// @@ -492,6 +633,7 @@ public bool TryCopyTo(Span destination) return false; } +#if SPAN_RUNTIME_SUPPORT /// /// Tries to get a instance, if the underlying buffer is contiguous. /// @@ -511,17 +653,35 @@ public bool TryAsSpan(out Span span) return false; } +#endif /// /// Provides an enumerator for the elements of a instance. /// public ref struct Enumerator { +#if SPAN_RUNTIME_SUPPORT /// /// The instance pointing to the first item in the target memory area. /// /// Just like in , the length is the height of the 2D region. private readonly Span span; +#else + /// + /// The target instance, if present. + /// + private readonly object? instance; + + /// + /// The initial offset within . + /// + private readonly IntPtr offset; + + /// + /// The height of the specified 2D region. + /// + private readonly int height; +#endif /// /// The width of the specified 2D region. @@ -549,7 +709,13 @@ public ref struct Enumerator /// The target instance to enumerate. internal Enumerator(Span2D span) { +#if SPAN_RUNTIME_SUPPORT this.span = span.span; +#else + this.instance = span.instance; + this.offset = span.offset; + this.height = span.height; +#endif this.width = span.width; this.pitch = span.pitch; this.x = -1; @@ -575,7 +741,13 @@ public bool MoveNext() // We reached the end of a row and there is at least // another row available: wrap to a new line and continue. - if (this.y < (this.span.Length - 1)) + if ( +#if SPAN_RUNTIME_SUPPORT + this.y < (this.span.Length - 1) +#else + this.y < this.height - 1 +#endif + ) { this.x = 0; this.y++; @@ -594,9 +766,14 @@ public ref T Current [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - return ref Unsafe.Add( - ref MemoryMarshal.GetReference(this.span), - (this.y * (this.width + this.pitch)) + this.x); +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref MemoryMarshal.GetReference(this.span); +#else + ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); +#endif + int index = (this.y * (this.width + this.pitch)) + this.x; + + return ref Unsafe.Add(ref r0, index); } } } @@ -654,5 +831,3 @@ private static void ThrowArgumentExceptionForDestinationTooShort() } } } - -#endif From a57217127bf416df9cddbd454ef2e7bdc6f9fb81 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 00:20:32 +0200 Subject: [PATCH 013/200] Added IsCovariant extensions for arrays --- .../Extensions/ArrayExtensions.1D.cs | 22 +++++++++++++++++++ .../Extensions/ArrayExtensions.2D.cs | 22 +++++++++++++++++++ .../Extensions/ArrayExtensions.3D.cs | 22 +++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs index 73e3b8ed73f..d9340365374 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs @@ -190,5 +190,27 @@ public static unsafe int GetDjb2HashCode(this T[] array) return SpanHelper.GetDjb2HashCode(ref r0, length); } + + /// + /// Checks whether or not a given array is covariant. + /// + /// The type of items in the input array instance. + /// The input array instance. + /// Whether or not is covariant. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsCovariant(this T[] array) + { + return +#pragma warning disable SA1003 // Whitespace before ! operator +#if NETSTANDARD1_4 + + !System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(T)).IsValueType && +#else + !typeof(T).IsValueType && +#endif +#pragma warning restore SA1003 + array.GetType() != typeof(T[]); + } } } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index e4d2c9a2943..554f66f07d2 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -304,5 +304,27 @@ public static unsafe int GetDjb2HashCode(this T[,] array) return SpanHelper.GetDjb2HashCode(ref r0, length); } + + /// + /// Checks whether or not a given array is covariant. + /// + /// The type of items in the input array instance. + /// The input array instance. + /// Whether or not is covariant. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsCovariant(this T[,] array) + { + return +#pragma warning disable SA1003 // Whitespace before ! operator +#if NETSTANDARD1_4 + + !System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(T)).IsValueType && +#else + !typeof(T).IsValueType && +#endif +#pragma warning restore SA1003 + array.GetType() != typeof(T[]); + } } } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index 64cd592049f..a5a5645a192 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -175,5 +175,27 @@ public static int GetDjb2HashCode(this T[,,] array) return SpanHelper.GetDjb2HashCode(ref r0, length); } + + /// + /// Checks whether or not a given array is covariant. + /// + /// The type of items in the input array instance. + /// The input array instance. + /// Whether or not is covariant. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsCovariant(this T[,,] array) + { + return +#pragma warning disable SA1003 // Whitespace before ! operator +#if NETSTANDARD1_4 + + !System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(T)).IsValueType && +#else + !typeof(T).IsValueType && +#endif +#pragma warning restore SA1003 + array.GetType() != typeof(T[]); + } } } From d6d50ea6abc9db04e767d2ebaba6616391cb3d6c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 00:21:07 +0200 Subject: [PATCH 014/200] Minor code refactoring --- .../Memory/Memory2D{T}.cs | 8 +++---- .../Memory/Span2D{T}.cs | 23 ++----------------- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 6d94a299e8c..09a9f23e938 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -64,7 +64,7 @@ namespace Microsoft.Toolkit.HighPerformance.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(T[] array, int offset, int width, int height) { - if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) + if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); } @@ -105,7 +105,7 @@ public Memory2D(T[,]? array) return; } - if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) + if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); } @@ -135,7 +135,7 @@ public Memory2D(T[,]? array) [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(T[,] array, int row, int column, int width, int height) { - if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) + if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); } @@ -171,7 +171,7 @@ public Memory2D(T[,] array, int row, int column, int width, int height) [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(T[,,] array, int depth) { - if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) + if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 093b5909c23..d26dc3bd5d7 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -5,7 +5,6 @@ using System; using System.ComponentModel; using System.Diagnostics.Contracts; -using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Extensions; @@ -147,16 +146,7 @@ public Span2D(T[,]? array) return; } - if ( -#pragma warning disable SA1003 // Whitespace before ! operator -#if NETSTANDARD1_4 - - !typeof(T).GetTypeInfo().IsValueType && -#else - !typeof(T).IsValueType && -#endif -#pragma warning restore SA1003 - array.GetType() != typeof(T[])) + if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); } @@ -189,16 +179,7 @@ public Span2D(T[,]? array) /// public Span2D(T[,] array, int row, int column, int width, int height) { - if ( -#pragma warning disable SA1003 // Whitespace before ! operator -#if NETSTANDARD1_4 - - !typeof(T).GetTypeInfo().IsValueType && -#else - !typeof(T).IsValueType && -#endif -#pragma warning restore SA1003 - array.GetType() != typeof(T[])) + if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); } From 20d3b118431c6eff9b6c6e8a4620e21f273217a0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 01:20:53 +0200 Subject: [PATCH 015/200] Enabled Memory2D on all targets --- .../Memory/Memory2D{T}.cs | 21 +++++++++++++++---- .../Memory/Span2D{T}.cs | 17 +++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 09a9f23e938..6e980e7c293 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if SPAN_RUNTIME_SUPPORT - using System; using System.Buffers; using System.ComponentModel; @@ -250,9 +248,13 @@ public Span2D Span { if (!(this.instance is null)) { +#if SPAN_RUNTIME_SUPPORT ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset); return new Span2D(ref r0, this.height, this.width, this.pitch); +#else + return new Span2D(this.instance, this.offset, this.height, this.width, this.pitch); +#endif } return default; @@ -329,12 +331,25 @@ public override int GetHashCode() { if (!(this.instance is null)) { +#if SPAN_RUNTIME_SUPPORT return HashCode.Combine( RuntimeHelpers.GetHashCode(this.instance), this.offset, this.height, this.width, this.pitch); +#else + Span values = stackalloc int[] + { + RuntimeHelpers.GetHashCode(this.instance), + this.offset.GetHashCode(), + this.height, + this.width, + this.pitch + }; + + return values.GetDjb2HashCode(); +#endif } return 0; @@ -349,5 +364,3 @@ private static void ThrowArrayTypeMismatchException() } } } - -#endif diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index d26dc3bd5d7..51798c0061b 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -128,6 +128,23 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) this.width = width; this.pitch = pitch; } +#else + /// + /// Initializes a new instance of the struct with the specified parameters. + /// + /// The target instance. + /// The initial offset within . + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map. + internal Span2D(object instance, IntPtr offset, int height, int width, int pitch) + { + this.instance = instance; + this.offset = offset; + this.height = height; + this.width = width; + this.pitch = pitch; + } #endif /// From 1a7a27ed563327e665ec032d14668c9a29b4401f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 01:39:14 +0200 Subject: [PATCH 016/200] Reordered some APIs --- .../Memory/Memory2D{T}.cs | 58 ++--- .../Memory/Span2D{T}.cs | 238 +++++++++--------- 2 files changed, 149 insertions(+), 147 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 6e980e7c293..8fcb0e286d7 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -186,11 +186,6 @@ public Memory2D(T[,,] array, int depth) this.pitch = 0; } - /// - /// Defines an implicit conversion of an array to a - /// - public static implicit operator Memory2D(T[,]? array) => new Memory2D(array); - /// /// Gets an empty instance. /// @@ -214,30 +209,6 @@ public int Length get => this.height * this.width; } - /// - public override string ToString() - { - return $"Microsoft.Toolkit.HighPerformance.Memory.Memory2D<{typeof(T)}>[{this.height}, {this.width}]"; - } - - /// - /// Slices the current instance with the specified parameters. - /// - /// The target row to map within the current instance. - /// The target column to map within the current instance. - /// The width to map within the current instance. - /// The height to map within the current instance. - /// - /// Thrown when either , or - /// are negative or not within the bounds that are valid for the current instance. - /// - /// A new instance representing a slice of the current one. - [Pure] - public Memory2D Slice(int row, int column, int width, int height) - { - throw new NotImplementedException("TODO"); - } - /// /// Gets a instance from the current memory. /// @@ -261,6 +232,24 @@ public Span2D Span } } + /// + /// Slices the current instance with the specified parameters. + /// + /// The target row to map within the current instance. + /// The target column to map within the current instance. + /// The width to map within the current instance. + /// The height to map within the current instance. + /// + /// Thrown when either , or + /// are negative or not within the bounds that are valid for the current instance. + /// + /// A new instance representing a slice of the current one. + [Pure] + public Memory2D Slice(int row, int column, int width, int height) + { + throw new NotImplementedException("TODO"); + } + /// /// Copies the contents of this into a destination instance. /// @@ -355,6 +344,17 @@ public override int GetHashCode() return 0; } + /// + public override string ToString() + { + return $"Microsoft.Toolkit.HighPerformance.Memory.Memory2D<{typeof(T)}>[{this.height}, {this.width}]"; + } + + /// + /// Defines an implicit conversion of an array to a + /// + public static implicit operator Memory2D(T[,]? array) => new Memory2D(array); + /// /// Throws an when using an array of an invalid type. /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 51798c0061b..fd09179dd9e 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -245,6 +245,22 @@ public bool IsEmpty } } + /// + /// Gets the length of the current instance. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { +#if SPAN_RUNTIME_SUPPORT + return this.span.Length * this.width; +#else + return this.height * this.width; +#endif + } + } + /// /// Gets the element at the specified zero-based indices. /// @@ -283,22 +299,6 @@ public bool IsEmpty } } - /// - /// Gets the length of the current instance. - /// - public int Length - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { -#if SPAN_RUNTIME_SUPPORT - return this.span.Length * this.width; -#else - return this.height * this.width; -#endif - } - } - /// /// Clears the contents of the current instance. /// @@ -395,23 +395,22 @@ public void CopyTo(Span2D destination) throw new NotImplementedException("TODO"); } -#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Equals() on Span will always throw an exception. Use == instead.")] - public override bool Equals(object obj) + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operaation was successful. + public bool TryCopyTo(Span destination) { - throw new NotSupportedException("Microsoft.Toolkit.HighPerformance.Span2D.Equals(object) is not supported"); - } + if (destination.Length >= Length) + { + CopyTo(destination); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("GetHashCode() on Span will always throw an exception.")] - public override int GetHashCode() - { - throw new NotSupportedException("Microsoft.Toolkit.HighPerformance.Span2D.GetHashCode() is not supported"); + return true; + } + + return false; } -#pragma warning restore CS0809 /// /// Fills the elements of this span with a specified value. @@ -496,43 +495,6 @@ public ref T DangerousGetReference() #endif } - /// - /// Checks whether two instances are equal. - /// - /// The first instance to compare. - /// The second instance to compare. - /// Whether or not and are equal. - public static bool operator ==(Span2D left, Span2D right) - { - return -#if SPAN_RUNTIME_SUPPORT - left.span == right.span && -#else - ReferenceEquals(left.instance, right.instance) && - left.offset == right.offset && - left.height == right.height && -#endif - left.width == right.width && - left.pitch == right.pitch; - } - - /// - /// Checks whether two instances are not equal. - /// - /// The first instance to compare. - /// The second instance to compare. - /// Whether or not and are not equal. - public static bool operator !=(Span2D left, Span2D right) - { - return !(left == right); - } - - /// - /// Implicily converts a given 2D array into a instance. - /// - /// The input 2D array to convert. - public static implicit operator Span2D(T[,]? array) => new Span2D(array); - /// /// Slices the current instance with the specified parameters. /// @@ -551,6 +513,28 @@ public Span2D Slice(int row, int column, int width, int height) throw new NotImplementedException("TODO"); } +#if SPAN_RUNTIME_SUPPORT + /// + /// Tries to get a instance, if the underlying buffer is contiguous. + /// + /// The resulting , in case of success. + /// Whether or not was correctly assigned. + public bool TryAsSpan(out Span span) + { + if (this.pitch == 0) + { + // We can only create a Span if the buffer is contiguous + span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Length); + + return true; + } + + span = default; + + return false; + } +#endif + /// /// Copies the contents of the current instance into a new 2D array. /// @@ -602,6 +586,24 @@ public Span2D Slice(int row, int column, int width, int height) return array; } +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Equals() on Span will always throw an exception. Use == instead.")] + public override bool Equals(object obj) + { + throw new NotSupportedException("Microsoft.Toolkit.HighPerformance.Span2D.Equals(object) is not supported"); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("GetHashCode() on Span will always throw an exception.")] + public override int GetHashCode() + { + throw new NotSupportedException("Microsoft.Toolkit.HighPerformance.Span2D.GetHashCode() is not supported"); + } +#pragma warning restore CS0809 + /// public override string ToString() { @@ -615,43 +617,41 @@ public override string ToString() } /// - /// Attempts to copy the current instance to a destination . + /// Checks whether two instances are equal. /// - /// The target of the copy operation. - /// Whether or not the operaation was successful. - public bool TryCopyTo(Span destination) + /// The first instance to compare. + /// The second instance to compare. + /// Whether or not and are equal. + public static bool operator ==(Span2D left, Span2D right) { - if (destination.Length >= Length) - { - CopyTo(destination); - - return true; - } - - return false; + return +#if SPAN_RUNTIME_SUPPORT + left.span == right.span && +#else + ReferenceEquals(left.instance, right.instance) && + left.offset == right.offset && + left.height == right.height && +#endif + left.width == right.width && + left.pitch == right.pitch; } -#if SPAN_RUNTIME_SUPPORT /// - /// Tries to get a instance, if the underlying buffer is contiguous. + /// Checks whether two instances are not equal. /// - /// The resulting , in case of success. - /// Whether or not was correctly assigned. - public bool TryAsSpan(out Span span) + /// The first instance to compare. + /// The second instance to compare. + /// Whether or not and are not equal. + public static bool operator !=(Span2D left, Span2D right) { - if (this.pitch == 0) - { - // We can only create a Span if the buffer is contiguous - span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Length); - - return true; - } - - span = default; - - return false; + return !(left == right); } -#endif + + /// + /// Implicily converts a given 2D array into a instance. + /// + /// The input 2D array to convert. + public static implicit operator Span2D(T[,]? array) => new Span2D(array); /// /// Provides an enumerator for the elements of a instance. @@ -776,56 +776,58 @@ public ref T Current } } +#if SPAN_RUNTIME_SUPPORT /// - /// Throws an when the a given coordinate is invalid. + /// Throws an when using the * constructor with a managed type. /// - /// - /// Throwing is technically discouraged in the docs, but - /// we're doing that here for consistency with the official type from the BCL. - /// - private static void ThrowIndexOutOfRangeException() + private static void ThrowArgumentExceptionForManagedType() { - throw new IndexOutOfRangeException(); + throw new ArgumentException("Can't create a Span2D from a pointer when T is a managed type"); } /// - /// Throws an when using the * constructor with a managed type. + /// Throws an when a constructor parameter is negative. /// - private static void ThrowArgumentExceptionForManagedType() + private static void ThrowArgumentExceptionForNegativeSize() { - throw new ArgumentException("Can't create a Span2D from a pointer when T is a managed type"); + throw new ArgumentException("The size parameters must be positive values"); } /// - /// Throws an when using an array of an invalid type. + /// Throws an when the target span is too short. /// - private static void ThrowArrayTypeMismatchException() + private static void ThrowArgumentExceptionForDestinationTooShort() { - throw new ArrayTypeMismatchException("The given array doesn't match the specified Span2D type"); + throw new ArgumentException("The target span is too short to copy all the current items to"); } +#endif /// - /// Throws an when a constructor parameter is negative. + /// Throws an when the a given coordinate is invalid. /// - private static void ThrowArgumentExceptionForNegativeSize() + /// + /// Throwing is technically discouraged in the docs, but + /// we're doing that here for consistency with the official type from the BCL. + /// + private static void ThrowIndexOutOfRangeException() { - throw new ArgumentException("The size parameters must be positive values"); + throw new IndexOutOfRangeException(); } /// - /// Throws an when a constructor parameter is negative or invalid. + /// Throws an when using an array of an invalid type. /// - private static void ThrowArgumentExceptionForNegativeOrInvalidParameter() + private static void ThrowArrayTypeMismatchException() { - throw new ArgumentException("The given parameters must be non negative and within valid range for the array"); + throw new ArrayTypeMismatchException("The given array doesn't match the specified Span2D type"); } /// - /// Throws an when the target span is too short. + /// Throws an when a constructor parameter is negative or invalid. /// - private static void ThrowArgumentExceptionForDestinationTooShort() + private static void ThrowArgumentExceptionForNegativeOrInvalidParameter() { - throw new ArgumentException("The target span is too short to copy all the current items to"); + throw new ArgumentException("The given parameters must be non negative and within valid range for the array"); } } } From 2de43c2c9bf7a7fec58e7751b60ba3175191c0c2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 02:10:44 +0200 Subject: [PATCH 017/200] Draft for Memory2D.Slice --- .../Memory/Memory2D{T}.cs | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 8fcb0e286d7..3fae416f966 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -186,6 +186,24 @@ public Memory2D(T[,,] array, int depth) this.pitch = 0; } + /// + /// Initializes a new instance of the struct with the specified parameters. + /// + /// The target instance. + /// The initial offset within . + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Memory2D(object instance, IntPtr offset, int height, int width, int pitch) + { + this.instance = instance; + this.offset = offset; + this.height = height; + this.width = width; + this.pitch = pitch; + } + /// /// Gets an empty instance. /// @@ -245,9 +263,20 @@ public Span2D Span /// /// A new instance representing a slice of the current one. [Pure] - public Memory2D Slice(int row, int column, int width, int height) + public unsafe Memory2D Slice(int row, int column, int width, int height) { - throw new NotImplementedException("TODO"); + if ((uint)row >= this.height || + (uint)column >= this.width || + (uint)width > (this.width - column) || + (uint)height > (this.height - row)) + { + throw new Exception(); + } + + int shift = ((this.width + this.pitch) * row) + column; + IntPtr offset = (IntPtr)((byte*)this.offset + shift); + + return new Memory2D(this.instance!, offset, height, width, this.pitch); } /// From b3e757ec7ba03c40931d1aa5483ddceec24b81be Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 02:44:16 +0200 Subject: [PATCH 018/200] Span2D refactoring, added Height/Width properties --- .../Memory/Span2D{T}.cs | 88 +++++++++++-------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index fd09179dd9e..5c555f0d93b 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -235,32 +235,43 @@ public Span2D(T[,] array, int row, int column, int width, int height) public bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { -#if SPAN_RUNTIME_SUPPORT - return (this.span.Length | this.width) == 0; -#else - return (this.height | this.width) == 0; -#endif - } + get => (Height | Width) == 0; } /// /// Gets the length of the current instance. /// - public int Length + public int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Height * Width; + } + + /// + /// Gets the height of the underlying 2D memory area. + /// + public int Height { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { #if SPAN_RUNTIME_SUPPORT - return this.span.Length * this.width; + return this.span.Length; #else - return this.height * this.width; + return this.height; #endif } } + /// + /// Gets the width of the underlying 2D memory area. + /// + public int Width + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.width; + } + /// /// Gets the element at the specified zero-based indices. /// @@ -275,15 +286,8 @@ public int Length [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ( -#pragma warning disable SA1003 // Whitespace before cast -#if SPAN_RUNTIME_SUPPORT - (uint)i >= (uint)this.span.Length || -#else - (uint)i >= (uint)this.height || -#endif -#pragma warning restore SA1003 - (uint)j >= (uint)this.width) + if ((uint)i >= (uint)Height || + (uint)j >= (uint)Width) { ThrowIndexOutOfRangeException(); } @@ -310,7 +314,7 @@ public void Clear() // If the pitch is 0, it means all the target area is contiguous // in memory with no padding between row boundaries. In this case // we can just create a Span over the area and use it to clear it. - MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Length).Clear(); + MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Size).Clear(); } else { @@ -347,11 +351,11 @@ public void CopyTo(Span destination) if (this.pitch == 0) { // If the pitch is 0, we can copy in a single pass - MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Length).CopyTo(destination); + MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Size).CopyTo(destination); } else { - if (Length > destination.Length) + if (Size > destination.Length) { ThrowArgumentExceptionForDestinationTooShort(); } @@ -399,10 +403,28 @@ public void CopyTo(Span2D destination) /// Attempts to copy the current instance to a destination . /// /// The target of the copy operation. - /// Whether or not the operaation was successful. + /// Whether or not the operation was successful. public bool TryCopyTo(Span destination) { - if (destination.Length >= Length) + if (destination.Length >= Size) + { + CopyTo(destination); + + return true; + } + + return false; + } + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operation was successful. + public bool TryCopyTo(Span2D destination) + { + if (destination.Height == Height && + destination.Width == Width) { CopyTo(destination); @@ -421,7 +443,7 @@ public void Fill(T value) #if SPAN_RUNTIME_SUPPORT if (this.pitch == 0) { - MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Length).Fill(value); + MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Size).Fill(value); } else { @@ -468,13 +490,9 @@ public unsafe ref T GetPinnableReference() { ref T r0 = ref Unsafe.AsRef(null); - if (this.Length != 0) + if (Size != 0) { -#if SPAN_RUNTIME_SUPPORT - r0 = ref MemoryMarshal.GetReference(this.span); -#else - r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); -#endif + r0 = ref this.DangerousGetReference(); } return ref r0; @@ -524,7 +542,7 @@ public bool TryAsSpan(out Span span) if (this.pitch == 0) { // We can only create a Span if the buffer is contiguous - span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Length); + span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Size); return true; } @@ -568,7 +586,7 @@ public bool TryAsSpan(out Span span) T[,] array = new T[this.height, this.width]; // Skip the initialization if the array is empty - if (Length > 0) + if (Size > 0) { ref T r0 = ref array.DangerousGetReference(); IntPtr offset = default; @@ -590,7 +608,7 @@ public bool TryAsSpan(out Span span) /// [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Equals() on Span will always throw an exception. Use == instead.")] - public override bool Equals(object obj) + public override bool Equals(object? obj) { throw new NotSupportedException("Microsoft.Toolkit.HighPerformance.Span2D.Equals(object) is not supported"); } From 357f49ed60d76c5ef24a1b0c22e30293ef595561 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 03:15:41 +0200 Subject: [PATCH 019/200] Added ThrowHelper class for memory APIs --- .../Memory/ThrowHelper.cs | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 Microsoft.Toolkit.HighPerformance/Memory/ThrowHelper.cs diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ThrowHelper.cs b/Microsoft.Toolkit.HighPerformance/Memory/ThrowHelper.cs new file mode 100644 index 00000000000..8ebfa96379e --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Memory/ThrowHelper.cs @@ -0,0 +1,112 @@ +using System; + +namespace Microsoft.Toolkit.HighPerformance.Memory +{ + /// + /// A helper class to throw exceptions for memory types. + /// + internal static class ThrowHelper + { +#if SPAN_RUNTIME_SUPPORT + /// + /// Throws an when using the * constructor with a managed type. + /// + public static void ThrowArgumentExceptionForManagedType() + { + throw new ArgumentException("Can't create a Span2D from a pointer when T is a managed type"); + } + + /// + /// Throws an when the target span is too short. + /// + public static void ThrowArgumentExceptionForDestinationTooShort() + { + throw new ArgumentException("The target span is too short to copy all the current items to"); + } +#endif + + /// + /// Throws an when using an array of an invalid type. + /// + public static void ThrowArrayTypeMismatchException() + { + throw new ArrayTypeMismatchException("The given array doesn't match the specified type "); + } + + /// + /// Throws an when the a given coordinate is invalid. + /// + /// + /// Throwing is technically discouraged in the docs, but + /// we're doing that here for consistency with the official type(s) from the BCL. + /// + public static void ThrowIndexOutOfRangeException() + { + throw new IndexOutOfRangeException(); + } + + /// + /// Throws an when more than one parameter are invalid. + /// + public static void ThrowArgumentException() + { + throw new ArgumentException("One or more input parameters were invalid"); + } + + /// + /// Throws an when the "depth" parameter is invalid. + /// + public static void ThrowArgumentOutOfRangeExceptionForDepth() + { + throw new ArgumentOutOfRangeException("depth"); + } + + /// + /// Throws an when the "row" parameter is invalid. + /// + public static void ThrowArgumentOutOfRangeExceptionForRow() + { + throw new ArgumentOutOfRangeException("row"); + } + + /// + /// Throws an when the "column" parameter is invalid. + /// + public static void ThrowArgumentOutOfRangeExceptionForColumn() + { + throw new ArgumentOutOfRangeException("column"); + } + + /// + /// Throws an when the "offset" parameter is invalid. + /// + public static void ThrowArgumentOutOfRangeExceptionForOffset() + { + throw new ArgumentOutOfRangeException("offset"); + } + + /// + /// Throws an when the "height" parameter is invalid. + /// + public static void ThrowArgumentOutOfRangeExceptionForHeight() + { + throw new ArgumentOutOfRangeException("height"); + } + + /// + /// Throws an when the "width" parameter is invalid. + /// + public static void ThrowArgumentOutOfRangeExceptionForWidth() + { + throw new ArgumentOutOfRangeException("width"); + } + + /// + /// Throws an when the "pitch" parameter is invalid. + /// + public static void ThrowArgumentOutOfRangeExceptionForPitch() + { + throw new ArgumentOutOfRangeException("pitch"); + } + } +} From 98affab5ba58c5f087b1e347654aafd53f6ef5c7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 03:17:43 +0200 Subject: [PATCH 020/200] Code refactoring using ThrowHelper --- .../Memory/Memory2D{T}.cs | 58 +++++++----- .../Memory/Span2D{T}.cs | 90 +++++-------------- 2 files changed, 61 insertions(+), 87 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 3fae416f966..211d6f17efb 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -64,19 +64,19 @@ public Memory2D(T[] array, int offset, int width, int height) { if (array.IsCovariant()) { - ThrowArrayTypeMismatchException(); + ThrowHelper.ThrowArrayTypeMismatchException(); } if ((uint)offset >= (uint)array.Length) { - throw new Exception(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); } int remaining = array.Length - offset; if (((uint)width * (uint)height) > (uint)remaining) { - throw new Exception(); + ThrowHelper.ThrowArgumentException(); } this.instance = array; @@ -105,7 +105,7 @@ public Memory2D(T[,]? array) if (array.IsCovariant()) { - ThrowArrayTypeMismatchException(); + ThrowHelper.ThrowArrayTypeMismatchException(); } this.instance = array; @@ -131,23 +131,47 @@ public Memory2D(T[,]? array) /// are negative or not within the bounds that are valid for . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory2D(T[,] array, int row, int column, int width, int height) + public Memory2D(T[,]? array, int row, int column, int width, int height) { + if (array is null) + { + if ((row | column | width | height) != 0) + { + ThrowHelper.ThrowArgumentException(); + } + + this = default; + + return; + } + if (array.IsCovariant()) { - ThrowArrayTypeMismatchException(); + ThrowHelper.ThrowArrayTypeMismatchException(); } int rows = array.GetLength(0), columns = array.GetLength(1); - if ((uint)row >= (uint)rows || - (uint)column >= (uint)columns || - width > (columns - column) || - height > (rows - row)) + if ((uint)row >= (uint)rows) { - throw new Exception(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + if ((uint)column >= (uint)columns) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + if (width > (columns - column)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (height > (rows - row)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } this.instance = array; @@ -171,12 +195,12 @@ public Memory2D(T[,,] array, int depth) { if (array.IsCovariant()) { - ThrowArrayTypeMismatchException(); + ThrowHelper.ThrowArrayTypeMismatchException(); } if ((uint)depth >= (uint)array.GetLength(0)) { - throw new Exception(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); } this.instance = array; @@ -383,13 +407,5 @@ public override string ToString() /// Defines an implicit conversion of an array to a /// public static implicit operator Memory2D(T[,]? array) => new Memory2D(array); - - /// - /// Throws an when using an array of an invalid type. - /// - private static void ThrowArrayTypeMismatchException() - { - throw new ArrayTypeMismatchException("The given array doesn't match the specified Span2D type"); - } } } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 5c555f0d93b..d6af142bf7d 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -93,7 +93,7 @@ public Span2D(ref T value, int height, int width, int pitch) { if ((height | width | pitch) < 0) { - ThrowArgumentExceptionForNegativeSize(); + ThrowHelper.ThrowArgumentException(); } this.span = MemoryMarshal.CreateSpan(ref value, height); @@ -116,12 +116,12 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) { if (RuntimeHelpers.IsReferenceOrContainsReferences()) { - ThrowArgumentExceptionForManagedType(); + ThrowHelper.ThrowArgumentExceptionForManagedType(); } if ((height | width | pitch) < 0) { - ThrowArgumentExceptionForNegativeSize(); + ThrowHelper.ThrowArgumentException(); } this.span = new Span(pointer, height); @@ -165,7 +165,7 @@ public Span2D(T[,]? array) if (array.IsCovariant()) { - ThrowArrayTypeMismatchException(); + ThrowHelper.ThrowArrayTypeMismatchException(); } #if SPAN_RUNTIME_SUPPORT @@ -198,19 +198,31 @@ public Span2D(T[,] array, int row, int column, int width, int height) { if (array.IsCovariant()) { - ThrowArrayTypeMismatchException(); + ThrowHelper.ThrowArrayTypeMismatchException(); } int rows = array.GetLength(0), columns = array.GetLength(1); - if ((uint)row >= (uint)rows || - (uint)column >= (uint)columns || - width > (columns - column) || - height > (rows - row)) + if ((uint)row >= (uint)rows) { - ThrowArgumentExceptionForNegativeOrInvalidParameter(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + if ((uint)column >= (uint)columns) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + if (width > (columns - column)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (height > (rows - row)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } #if SPAN_RUNTIME_SUPPORT @@ -289,7 +301,7 @@ public int Width if ((uint)i >= (uint)Height || (uint)j >= (uint)Width) { - ThrowIndexOutOfRangeException(); + ThrowHelper.ThrowIndexOutOfRangeException(); } #if SPAN_RUNTIME_SUPPORT @@ -357,7 +369,7 @@ public void CopyTo(Span destination) { if (Size > destination.Length) { - ThrowArgumentExceptionForDestinationTooShort(); + ThrowHelper.ThrowArgumentExceptionForDestinationTooShort(); } ref T sourceRef = ref MemoryMarshal.GetReference(this.span); @@ -793,59 +805,5 @@ public ref T Current } } } - -#if SPAN_RUNTIME_SUPPORT - /// - /// Throws an when using the * constructor with a managed type. - /// - private static void ThrowArgumentExceptionForManagedType() - { - throw new ArgumentException("Can't create a Span2D from a pointer when T is a managed type"); - } - - /// - /// Throws an when a constructor parameter is negative. - /// - private static void ThrowArgumentExceptionForNegativeSize() - { - throw new ArgumentException("The size parameters must be positive values"); - } - - /// - /// Throws an when the target span is too short. - /// - private static void ThrowArgumentExceptionForDestinationTooShort() - { - throw new ArgumentException("The target span is too short to copy all the current items to"); - } -#endif - - /// - /// Throws an when the a given coordinate is invalid. - /// - /// - /// Throwing is technically discouraged in the docs, but - /// we're doing that here for consistency with the official type from the BCL. - /// - private static void ThrowIndexOutOfRangeException() - { - throw new IndexOutOfRangeException(); - } - - /// - /// Throws an when using an array of an invalid type. - /// - private static void ThrowArrayTypeMismatchException() - { - throw new ArrayTypeMismatchException("The given array doesn't match the specified Span2D type"); - } - - /// - /// Throws an when a constructor parameter is negative or invalid. - /// - private static void ThrowArgumentExceptionForNegativeOrInvalidParameter() - { - throw new ArgumentException("The given parameters must be non negative and within valid range for the array"); - } } } From d5991b0a97ba68119ef76e09ce25ab54e08af857 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 12:31:30 +0200 Subject: [PATCH 021/200] Added Memory constructor to Memory2D --- .../Memory/Memory2D{T}.cs | 58 +++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 211d6f17efb..e4de089651f 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -16,9 +16,9 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// represents a 2D region of arbitrary memory. It is to /// what is to . For further details on how the internal layout /// is structured, see the docs for . The type can wrap arrays - /// of any rank, provided that a valid series of parameters for the target memory area(s) are provided. + /// of any rank, provided that a valid series of parameters for the target memory area(s) are specified. /// - /// The type of items in the current instance. + /// The type of items in the current instance. public readonly struct Memory2D : IEquatable> { /// @@ -210,6 +210,40 @@ public Memory2D(T[,,] array, int depth) this.pitch = 0; } +#if SPAN_RUNTIME_SUPPORT + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// + /// Thrown when either , or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory2D(Memory memory, int offset, int width, int height) + { + if ((uint)offset >= (uint)memory.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); + } + + int remaining = memory.Length - offset; + + if (((uint)width * (uint)height) > (uint)remaining) + { + ThrowHelper.ThrowArgumentException(); + } + + this.instance = memory; + this.offset = (IntPtr)offset; + this.height = height; + this.width = width; + this.pitch = 0; + } +#endif + /// /// Initializes a new instance of the struct with the specified parameters. /// @@ -262,9 +296,23 @@ public Span2D Span if (!(this.instance is null)) { #if SPAN_RUNTIME_SUPPORT - ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset); - - return new Span2D(ref r0, this.height, this.width, this.pitch); + if (this.instance.GetType() == typeof(Memory)) + { + Memory memory = (Memory)this.instance; + + // If the wrapped object is a Memory, the offset simply refers + // to the initial distance from the first element in the span. + ref T r0 = ref memory.Span.DangerousGetReferenceAt((int)this.offset); + + return new Span2D(ref r0, this.height, this.width, this.pitch); + } + else + { + // The only other possible cases is with the instance being an array + ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset); + + return new Span2D(ref r0, this.height, this.width, this.pitch); + } #else return new Span2D(this.instance, this.offset, this.height, this.width, this.pitch); #endif From a6ef3003a8356aa2240ff3cedc04b014fa79e464 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 12:34:59 +0200 Subject: [PATCH 022/200] More fixes to Memory2D --- .../Memory/Memory2D{T}.cs | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index e4de089651f..09d3d516e94 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -337,12 +337,24 @@ public Span2D Span [Pure] public unsafe Memory2D Slice(int row, int column, int width, int height) { - if ((uint)row >= this.height || - (uint)column >= this.width || - (uint)width > (this.width - column) || - (uint)height > (this.height - row)) + if ((uint)row >= this.height) { - throw new Exception(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + if ((uint)column >= this.width) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + if ((uint)width > (this.width - column)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if ((uint)height > (this.height - row)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } int shift = ((this.width + this.pitch) * row) + column; @@ -367,6 +379,22 @@ public unsafe Memory2D Slice(int row, int column, int width, int height) /// Whether or not the operaation was successful. public bool TryCopyTo(Memory destination) => Span.TryCopyTo(destination.Span); + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(Memory2D destination) => Span.CopyTo(destination.Span); + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operaation was successful. + public bool TryCopyTo(Memory2D destination) => Span.TryCopyTo(destination.Span); + /// /// Creates a handle for the memory. /// The GC will not move the memory until the returned From 9942e4ed6bf00fc4ea40c8890c98a2e4b9273bb8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 12:41:28 +0200 Subject: [PATCH 023/200] Added Memory2D constructors with pitch --- .../Memory/Memory2D{T}.cs | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 09d3d516e94..0d4e6d7d59b 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -61,6 +61,27 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(T[] array, int offset, int width, int height) + : this(array, offset, width, height, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target array to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// The pitch in the resulting 2D area. + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , , + /// or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory2D(T[] array, int offset, int width, int height, int pitch) { if (array.IsCovariant()) { @@ -74,7 +95,7 @@ public Memory2D(T[] array, int offset, int width, int height) int remaining = array.Length - offset; - if (((uint)width * (uint)height) > (uint)remaining) + if ((((uint)width + (uint)pitch) * (uint)height) > (uint)remaining) { ThrowHelper.ThrowArgumentException(); } @@ -83,7 +104,7 @@ public Memory2D(T[] array, int offset, int width, int height) this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)); this.height = height; this.width = width; - this.pitch = 0; + this.pitch = pitch; } /// @@ -223,6 +244,24 @@ public Memory2D(T[,,] array, int depth) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(Memory memory, int offset, int width, int height) + : this(memory, offset, width, height, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// The pitch in the resulting 2D area. + /// + /// Thrown when either , , + /// or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory2D(Memory memory, int offset, int width, int height, int pitch) { if ((uint)offset >= (uint)memory.Length) { @@ -231,7 +270,7 @@ public Memory2D(Memory memory, int offset, int width, int height) int remaining = memory.Length - offset; - if (((uint)width * (uint)height) > (uint)remaining) + if ((((uint)width + (uint)pitch) * (uint)height) > (uint)remaining) { ThrowHelper.ThrowArgumentException(); } @@ -240,7 +279,7 @@ public Memory2D(Memory memory, int offset, int width, int height) this.offset = (IntPtr)offset; this.height = height; this.width = width; - this.pitch = 0; + this.pitch = pitch; } #endif From 8a246c8f4f8710ec514684bc2255d82bc8d3f407 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 12:46:08 +0200 Subject: [PATCH 024/200] Enabled Span2D.TryGetSpan on .NET Standard < 2.1 --- .../Memory/Span2D{T}.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index d6af142bf7d..44b84e59a27 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -543,27 +543,36 @@ public Span2D Slice(int row, int column, int width, int height) throw new NotImplementedException("TODO"); } -#if SPAN_RUNTIME_SUPPORT /// /// Tries to get a instance, if the underlying buffer is contiguous. /// /// The resulting , in case of success. /// Whether or not was correctly assigned. - public bool TryAsSpan(out Span span) + public bool TryGetSpan(out Span span) { if (this.pitch == 0) { +#if SPAN_RUNTIME_SUPPORT // We can only create a Span if the buffer is contiguous span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Size); + return true; +#else + // Without Span runtime support, we can only get a Span from a T[] instance + if (this.instance?.GetType() == typeof(T[])) + { + span = Unsafe.As(this.instance).AsSpan((int)this.offset, Size); + + return true; + } +#endif } span = default; return false; } -#endif /// /// Copies the contents of the current instance into a new 2D array. From b52f573c94bf502a333509c8cadfe51768b236d5 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 12:54:19 +0200 Subject: [PATCH 025/200] Added Memory2D.TryGetMemory --- .../Memory/Memory2D{T}.cs | 50 +++++++++++++++++-- .../Memory/Span2D{T}.cs | 11 +++- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 0d4e6d7d59b..0de979d5323 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -275,8 +275,8 @@ public Memory2D(Memory memory, int offset, int width, int height, int pitch) ThrowHelper.ThrowArgumentException(); } - this.instance = memory; - this.offset = (IntPtr)offset; + this.instance = memory.Slice(offset); + this.offset = default; this.height = height; this.width = width; this.pitch = pitch; @@ -339,9 +339,8 @@ public Span2D Span { Memory memory = (Memory)this.instance; - // If the wrapped object is a Memory, the offset simply refers - // to the initial distance from the first element in the span. - ref T r0 = ref memory.Span.DangerousGetReferenceAt((int)this.offset); + // If the wrapped object is a Memory, it is always pre-offset + ref T r0 = ref memory.Span.DangerousGetReference(); return new Span2D(ref r0, this.height, this.width, this.pitch); } @@ -457,6 +456,47 @@ public unsafe MemoryHandle Pin() return default; } + /// + /// Tries to get a instance, if the underlying buffer is contiguous. + /// + /// The resulting , in case of success. + /// Whether or not was correctly assigned. + public bool TryGetMemory(out Memory memory) + { + if (this.pitch == 0) + { + // Empty Memory2D instance + if (this.instance is null) + { + memory = default; + } + else if (this.instance.GetType() == typeof(Memory)) + { + // If the object is a Memory, just slice it as needed + memory = ((Memory)this.instance).Slice(0, this.height * this.width); + } + else if (this.instance.GetType() == typeof(T[])) + { + // If it's a T[] array, also handle the initial offset + memory = Unsafe.As(this.instance).AsMemory((int)this.offset, this.height * this.width); + } + else + { + // Reuse a single failure path to reduce + // the number of returns in the method + goto Failure; + } + + return true; + } + + Failure: + + memory = default; + + return false; + } + /// /// Copies the contents of the current instance into a new 2D array. /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 44b84e59a27..df69060e009 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -556,11 +556,18 @@ public bool TryGetSpan(out Span span) // We can only create a Span if the buffer is contiguous span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Size); - return true; #else + // An empty Span2D is still valid + if (this.instance is null) + { + span = default; + + return true; + } + // Without Span runtime support, we can only get a Span from a T[] instance - if (this.instance?.GetType() == typeof(T[])) + if (this.instance.GetType() == typeof(T[])) { span = Unsafe.As(this.instance).AsSpan((int)this.offset, Size); From 09c0fc4acf8eeb31671255ab13ec6f5f3d4f4dbb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 13:30:04 +0200 Subject: [PATCH 026/200] Minor code refactoring --- .../Memory/Span2D{T}.Enumerator.cs | 148 ++++++++++++++++++ .../Memory/Span2D{T}.cs | 135 +--------------- 2 files changed, 149 insertions(+), 134 deletions(-) create mode 100644 Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs new file mode 100644 index 00000000000..534bb066aa3 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -0,0 +1,148 @@ +// 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; + +namespace Microsoft.Toolkit.HighPerformance.Memory +{ + /// + public readonly ref partial struct Span2D + { + /// + /// Returns an enumerator for the current instance. + /// + /// + /// An enumerator that can be used to traverse the items in the current instance + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new Enumerator(this); + + /// + /// Provides an enumerator for the elements of a instance. + /// + public ref struct Enumerator + { +#if SPAN_RUNTIME_SUPPORT + /// + /// The instance pointing to the first item in the target memory area. + /// + /// Just like in , the length is the height of the 2D region. + private readonly Span span; +#else + /// + /// The target instance, if present. + /// + private readonly object? instance; + + /// + /// The initial offset within . + /// + private readonly IntPtr offset; + + /// + /// The height of the specified 2D region. + /// + private readonly int height; +#endif + + /// + /// The width of the specified 2D region. + /// + private readonly int width; + + /// + /// The pitch of the specified 2D region. + /// + private readonly int pitch; + + /// + /// The current horizontal offset. + /// + private int x; + + /// + /// The current vertical offset. + /// + private int y; + + /// + /// Initializes a new instance of the struct. + /// + /// The target instance to enumerate. + internal Enumerator(Span2D span) + { +#if SPAN_RUNTIME_SUPPORT + this.span = span.span; +#else + this.instance = span.instance; + this.offset = span.offset; + this.height = span.height; +#endif + this.width = span.width; + this.pitch = span.pitch; + this.x = -1; + this.y = 0; + } + + /// + /// Implements the duck-typed method. + /// + /// whether a new element is available, otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + int x = this.x + 1; + + // Horizontal move, within range + if (x < this.width) + { + this.x = x; + + return true; + } + + // We reached the end of a row and there is at least + // another row available: wrap to a new line and continue. + if ( +#if SPAN_RUNTIME_SUPPORT + this.y < (this.span.Length - 1) +#else + this.y < this.height - 1 +#endif + ) + { + this.x = 0; + this.y++; + + return true; + } + + return false; + } + + /// + /// Gets the duck-typed property. + /// + public ref T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref MemoryMarshal.GetReference(this.span); +#else + ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); +#endif + int index = (this.y * (this.width + this.pitch)) + this.x; + + return ref Unsafe.Add(ref r0, index); + } + } + } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index df69060e009..8a1d61fc0c9 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -20,7 +20,7 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// type and it is transparent to the user, but note that working over discontiguous buffers has a performance impact. /// /// The type of items in the current instance. - public readonly ref struct Span2D + public readonly ref partial struct Span2D { // Let's consider a representation of a discontiguous 2D memory // region within an existing array. The data is represented in @@ -479,16 +479,6 @@ public void Fill(T value) #endif } - /// - /// Returns an enumerator for the current instance. - /// - /// - /// An enumerator that can be used to traverse the items in the current instance - /// - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator GetEnumerator() => new Enumerator(this); - /// /// Returns a reference to the 0th element of the instance. If the current /// instance is empty, returns a reference. It can be used for pinning @@ -698,128 +688,5 @@ public override string ToString() /// /// The input 2D array to convert. public static implicit operator Span2D(T[,]? array) => new Span2D(array); - - /// - /// Provides an enumerator for the elements of a instance. - /// - public ref struct Enumerator - { -#if SPAN_RUNTIME_SUPPORT - /// - /// The instance pointing to the first item in the target memory area. - /// - /// Just like in , the length is the height of the 2D region. - private readonly Span span; -#else - /// - /// The target instance, if present. - /// - private readonly object? instance; - - /// - /// The initial offset within . - /// - private readonly IntPtr offset; - - /// - /// The height of the specified 2D region. - /// - private readonly int height; -#endif - - /// - /// The width of the specified 2D region. - /// - private readonly int width; - - /// - /// The pitch of the specified 2D region. - /// - private readonly int pitch; - - /// - /// The current horizontal offset. - /// - private int x; - - /// - /// The current vertical offset. - /// - private int y; - - /// - /// Initializes a new instance of the struct. - /// - /// The target instance to enumerate. - internal Enumerator(Span2D span) - { -#if SPAN_RUNTIME_SUPPORT - this.span = span.span; -#else - this.instance = span.instance; - this.offset = span.offset; - this.height = span.height; -#endif - this.width = span.width; - this.pitch = span.pitch; - this.x = -1; - this.y = 0; - } - - /// - /// Implements the duck-typed method. - /// - /// whether a new element is available, otherwise - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { - int x = this.x + 1; - - // Horizontal move, within range - if (x < this.width) - { - this.x = x; - - return true; - } - - // We reached the end of a row and there is at least - // another row available: wrap to a new line and continue. - if ( -#if SPAN_RUNTIME_SUPPORT - this.y < (this.span.Length - 1) -#else - this.y < this.height - 1 -#endif - ) - { - this.x = 0; - this.y++; - - return true; - } - - return false; - } - - /// - /// Gets the duck-typed property. - /// - public ref T Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { -#if SPAN_RUNTIME_SUPPORT - ref T r0 = ref MemoryMarshal.GetReference(this.span); -#else - ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); -#endif - int index = (this.y * (this.width + this.pitch)) + this.x; - - return ref Unsafe.Add(ref r0, index); - } - } - } } } From 91f2f297cb2c48ec37bf5d537443b71348929da5 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 13:51:41 +0200 Subject: [PATCH 027/200] Implemented Span2D.CopyTo(Span2D) --- .../Memory/Span2D{T}.cs | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 8a1d61fc0c9..98975c9aafc 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -401,6 +401,7 @@ public void CopyTo(Span destination) /// /// Copies the contents of this into a destination instance. + /// For this API to succeed, the target has to have the same shape as the current one. /// /// The destination instance. /// @@ -408,7 +409,49 @@ public void CopyTo(Span destination) /// public void CopyTo(Span2D destination) { - throw new NotImplementedException("TODO"); + if (destination.Height != Height || + destination.width != Width) + { + ThrowHelper.ThrowArgumentException(); + } + + if (destination.TryGetSpan(out Span span)) + { + CopyTo(span); + } + else + { +#if SPAN_RUNTIME_SUPPORT + int + sourcePaddedWitdh = this.width + this.pitch, + destinationPaddedWidth = destination.width + destination.pitch, + sourcePaddedSize = sourcePaddedWitdh * Height, + destinationPaddedSize = destinationPaddedWidth * Height, + sourceOffset = 0, + destinationOffset = 0; + Span + fullSourceSpan = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), sourcePaddedSize), + fullDestinationSpan = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(destination.span), destinationPaddedSize); + + for (int i = 0; i < Height; i++) + { + fullSourceSpan.Slice(sourceOffset, this.width).CopyTo(fullDestinationSpan.Slice(destinationOffset, this.width)); + + sourceOffset += sourcePaddedSize; + destinationOffset += destinationPaddedSize; + } +#else + Enumerator destinationEnumerator = destination.GetEnumerator(); + + // Fallback path with two enumerators + foreach (T item in this) + { + _ = destinationEnumerator.MoveNext(); + + destinationEnumerator.Current = item; + } +#endif + } } /// From 4d187c36e98afacbf4c44364a64d0932d6cfd04a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 13:57:00 +0200 Subject: [PATCH 028/200] Implemented Span2D.Slice API --- .../Memory/Span2D{T}.cs | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 98975c9aafc..f1c2a1137ab 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -573,7 +573,41 @@ public ref T DangerousGetReference() [Pure] public Span2D Slice(int row, int column, int width, int height) { - throw new NotImplementedException("TODO"); + if ((uint)row >= Height) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + if ((uint)column >= this.width) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + if ((uint)width > (this.width - column)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if ((uint)height > (Height - row)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + int shift = ((this.width + this.pitch) * row) + column; + +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.span), shift); + int pitch = this.pitch + column; + + return new Span2D(ref r0, height, width, pitch); +#else + unsafe + { + IntPtr offset = (IntPtr)((byte*)this.offset + shift); + + return new Span2D(this.instance!, offset, height, width, this.pitch); + } +#endif } /// From 422cab9ba163bf02c3a6c48e068523bc82d0ace3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 14:52:44 +0200 Subject: [PATCH 029/200] Added [ReadOnly]RefEnumerable types --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 129 ++++++++++++++++++ .../Enumerables/RefEnumerable{T}.cs | 129 ++++++++++++++++++ .../Memory/Span2D{T}.cs | 23 ++++ 3 files changed, 281 insertions(+) create mode 100644 Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs create mode 100644 Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs new file mode 100644 index 00000000000..d8b520f1938 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -0,0 +1,129 @@ +using System; +using System.ComponentModel; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +#if SPAN_RUNTIME_SUPPORT +using System.Runtime.InteropServices; +#endif +using Microsoft.Toolkit.HighPerformance.Extensions; + +namespace Microsoft.Toolkit.HighPerformance.Enumerables +{ + /// + /// A that iterates readonly items from arbitrary memory locations. + /// + /// The type of items to enumerate. + [EditorBrowsable(EditorBrowsableState.Never)] + public ref struct ReadOnlyRefEnumerable + { + /// + /// The distance between items in the sequence to enumerate. + /// + private readonly int step; + +#if SPAN_RUNTIME_SUPPORT + /// + /// The instance pointing to the first item in the target memory area. + /// + /// The field maps to the total available length. + private readonly ReadOnlySpan span; +#else + /// + /// The target instance, if present. + /// + private readonly object? instance; + + /// + /// The initial offset within . + /// + private readonly IntPtr offset; + + /// + /// The total available length for the sequence. + /// + private readonly int length; +#endif + + /// + /// The current position in the sequence. + /// + private int position; + +#if SPAN_RUNTIME_SUPPORT + /// + /// Initializes a new instance of the struct. + /// + /// A reference to the first item of the sequence. + /// The total available length for the sequence. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlyRefEnumerable(ref T reference, int length, int step) + { + this.span = MemoryMarshal.CreateReadOnlySpan(ref reference, length); + this.step = step; + this.position = 0; + } +#else + /// + /// Initializes a new instance of the struct. + /// + /// The target instance. + /// The initial offset within . + /// The total available length for the sequence. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlyRefEnumerable(object instance, IntPtr offset, int length, int step) + { + this.instance = instance; + this.offset = offset; + this.length = length; + this.step = step; + this.position = 0; + } +#endif + + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyRefEnumerable GetEnumerator() => this; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + int position = this.position + this.step; + + if ( +#if SPAN_RUNTIME_SUPPORT + position < this.span.Length +#else + position < this.length +#endif + ) + { + this.position = position; + + return true; + } + + return false; + } + + /// + public ref readonly T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { +#if SPAN_RUNTIME_SUPPORT + return ref this.span.DangerousGetReferenceAt(this.position); +#else + ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + ref T ri = ref Unsafe.Add(ref r0, this.position); + + return ref ri; +#endif + } + } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs new file mode 100644 index 00000000000..24534c3d7d6 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -0,0 +1,129 @@ +using System; +using System.ComponentModel; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +#if SPAN_RUNTIME_SUPPORT +using System.Runtime.InteropServices; +#endif +using Microsoft.Toolkit.HighPerformance.Extensions; + +namespace Microsoft.Toolkit.HighPerformance.Enumerables +{ + /// + /// A that iterates items from arbitrary memory locations. + /// + /// The type of items to enumerate. + [EditorBrowsable(EditorBrowsableState.Never)] + public ref struct RefEnumerable + { + /// + /// The distance between items in the sequence to enumerate. + /// + private readonly int step; + +#if SPAN_RUNTIME_SUPPORT + /// + /// The instance pointing to the first item in the target memory area. + /// + /// The field maps to the total available length. + private readonly Span span; +#else + /// + /// The target instance, if present. + /// + private readonly object? instance; + + /// + /// The initial offset within . + /// + private readonly IntPtr offset; + + /// + /// The total available length for the sequence. + /// + private readonly int length; +#endif + + /// + /// The current position in the sequence. + /// + private int position; + +#if SPAN_RUNTIME_SUPPORT + /// + /// Initializes a new instance of the struct. + /// + /// A reference to the first item of the sequence. + /// The total available length for the sequence. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal RefEnumerable(ref T reference, int length, int step) + { + this.span = MemoryMarshal.CreateSpan(ref reference, length); + this.step = step; + this.position = 0; + } +#else + /// + /// Initializes a new instance of the struct. + /// + /// The target instance. + /// The initial offset within . + /// The total available length for the sequence. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal RefEnumerable(object instance, IntPtr offset, int length, int step) + { + this.instance = instance; + this.offset = offset; + this.length = length; + this.step = step; + this.position = 0; + } +#endif + + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RefEnumerable GetEnumerator() => this; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + int position = this.position + this.step; + + if ( +#if SPAN_RUNTIME_SUPPORT + position < this.span.Length +#else + position < this.length +#endif + ) + { + this.position = position; + + return true; + } + + return false; + } + + /// + public ref T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { +#if SPAN_RUNTIME_SUPPORT + return ref this.span.DangerousGetReferenceAt(this.position); +#else + ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + ref T ri = ref Unsafe.Add(ref r0, this.position); + + return ref ri; +#endif + } + } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index f1c2a1137ab..c915985ad6f 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -610,6 +610,29 @@ public Span2D Slice(int row, int column, int width, int height) #endif } +#if SPAN_RUNTIME_SUPPORT + /// + /// Gets a for a specified row. + /// + /// The index of the target row to retrieve. + /// Throw when is out of range. + /// The resulting row . + [Pure] + public Span GetRowSpan(int row) + { + if ((uint)row >= (uint)Height) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + int offset = (this.width + this.pitch) * row; + ref T r0 = ref MemoryMarshal.GetReference(this.span); + ref T r1 = ref Unsafe.Add(ref r0, offset); + + return MemoryMarshal.CreateSpan(ref r1, this.width); + } +#endif + /// /// Tries to get a instance, if the underlying buffer is contiguous. /// From 61be3b66a1bc52214b6f6e691492f674a83c045f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 15:06:16 +0200 Subject: [PATCH 030/200] Added [ReadOnly]RefEnumerable.ToArray() --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 50 +++++++++++++++++-- .../Enumerables/RefEnumerable{T}.cs | 50 +++++++++++++++++-- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index d8b520f1938..b9089a438cc 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -54,12 +54,12 @@ public ref struct ReadOnlyRefEnumerable /// Initializes a new instance of the struct. /// /// A reference to the first item of the sequence. - /// The total available length for the sequence. + /// The number of items in the sequence. /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ReadOnlyRefEnumerable(ref T reference, int length, int step) { - this.span = MemoryMarshal.CreateReadOnlySpan(ref reference, length); + this.span = MemoryMarshal.CreateReadOnlySpan(ref reference, length * step); this.step = step; this.position = 0; } @@ -69,14 +69,14 @@ internal ReadOnlyRefEnumerable(ref T reference, int length, int step) /// /// The target instance. /// The initial offset within . - /// The total available length for the sequence. + /// The number of items in the sequence. /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ReadOnlyRefEnumerable(object instance, IntPtr offset, int length, int step) { this.instance = instance; this.offset = offset; - this.length = length; + this.length = length * step; this.step = step; this.position = 0; } @@ -125,5 +125,47 @@ public ref readonly T Current #endif } } + + /// + /// Returns a array with the values in the target row. + /// + /// A array with the values in the target row. + /// + /// This method will allocate a new array, so only + /// use it if you really need to copy the target items in a new memory location. + /// Additionally, this method will always return the whole sequence from the start, + /// ignoring the current position in case the sequence has already been enumerated in part. + /// + [Pure] + public T[] ToArray() + { +#if SPAN_RUNTIME_SUPPORT + // Fast path for contiguous items + if (this.step == 1) + { + return this.span.ToArray(); + } + + int length = this.span.Length; +#else + int length = this.length; +#endif + + // Empty array if no data is mapped + if (length == 0) + { + return Array.Empty(); + } + + T[] array = new T[length / this.step]; + ref T r0 = ref array.DangerousGetReference(); + + for (int i = 0, j = 0; i < length; i += this.step, j++) + { + Unsafe.Add(ref r0, j) = Unsafe.Add(ref r0, i); + } + + return array; + } } } diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 24534c3d7d6..3d02afb8df0 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -54,12 +54,12 @@ public ref struct RefEnumerable /// Initializes a new instance of the struct. /// /// A reference to the first item of the sequence. - /// The total available length for the sequence. + /// The number of items in the sequence. /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal RefEnumerable(ref T reference, int length, int step) { - this.span = MemoryMarshal.CreateSpan(ref reference, length); + this.span = MemoryMarshal.CreateSpan(ref reference, length * step); this.step = step; this.position = 0; } @@ -69,14 +69,14 @@ internal RefEnumerable(ref T reference, int length, int step) /// /// The target instance. /// The initial offset within . - /// The total available length for the sequence. + /// The number of items in the sequence. /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal RefEnumerable(object instance, IntPtr offset, int length, int step) { this.instance = instance; this.offset = offset; - this.length = length; + this.length = length * step; this.step = step; this.position = 0; } @@ -125,5 +125,47 @@ public ref T Current #endif } } + + /// + /// Returns a array with the values in the target row. + /// + /// A array with the values in the target row. + /// + /// This method will allocate a new array, so only + /// use it if you really need to copy the target items in a new memory location. + /// Additionally, this method will always return the whole sequence from the start, + /// ignoring the current position in case the sequence has already been enumerated in part. + /// + [Pure] + public T[] ToArray() + { +#if SPAN_RUNTIME_SUPPORT + // Fast path for contiguous items + if (this.step == 1) + { + return this.span.ToArray(); + } + + int length = this.span.Length; +#else + int length = this.length; +#endif + + // Empty array if no data is mapped + if (length == 0) + { + return Array.Empty(); + } + + T[] array = new T[length / this.step]; + ref T r0 = ref array.DangerousGetReference(); + + for (int i = 0, j = 0; i < length; i += this.step, j++) + { + Unsafe.Add(ref r0, j) = Unsafe.Add(ref r0, i); + } + + return array; + } } } From 0413d804387c84362941d9db6b3fc4d3c54d6d25 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 15:07:11 +0200 Subject: [PATCH 031/200] Fixed a build error --- .../Memory/Span2D{T}.Enumerator.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index 534bb066aa3..a9927ef474e 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -5,7 +5,11 @@ using System; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; +#if SPAN_RUNTIME_SUPPORT using System.Runtime.InteropServices; +#else +using Microsoft.Toolkit.HighPerformance.Extensions; +#endif namespace Microsoft.Toolkit.HighPerformance.Memory { From 9f9dcfec28ad42717b045b905940c436b5b95582 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 15:16:09 +0200 Subject: [PATCH 032/200] Refactored 2D array extensions with new enumerables --- .../Enumerables/Array2DColumnEnumerable{T}.cs | 213 ------------------ .../Enumerables/Array2DRowEnumerable{T}.cs | 170 -------------- .../Extensions/ArrayExtensions.2D.cs | 48 ++-- 3 files changed, 22 insertions(+), 409 deletions(-) delete mode 100644 Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs delete mode 100644 Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs deleted file mode 100644 index 629d5f903f8..00000000000 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs +++ /dev/null @@ -1,213 +0,0 @@ -// 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.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics.Contracts; -using System.Runtime.CompilerServices; -using Microsoft.Toolkit.HighPerformance.Extensions; - -namespace Microsoft.Toolkit.HighPerformance.Enumerables -{ - /// - /// A that iterates a column in a given 2D array instance. - /// - /// The type of items to enumerate. - [EditorBrowsable(EditorBrowsableState.Never)] - public readonly ref struct Array2DColumnEnumerable - { - /// - /// The source 2D array instance. - /// - private readonly T[,] array; - - /// - /// The target column to iterate within . - /// - private readonly int column; - - /// - /// Initializes a new instance of the struct. - /// - /// The source 2D array instance. - /// The target column to iterate within . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Array2DColumnEnumerable(T[,] array, int column) - { - this.array = array; - this.column = column; - } - - /// - /// Implements the duck-typed method. - /// - /// An instance targeting the current 2D array instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator GetEnumerator() => new Enumerator(this.array, this.column); - - /// - /// Returns a array with the values in the target column. - /// - /// A array with the values in the target column. - /// - /// This method will allocate a new array, so only - /// use it if you really need to copy the target items in a new memory location. - /// - [Pure] - public T[] ToArray() - { - if ((uint)column >= (uint)this.array.GetLength(1)) - { - ThrowArgumentOutOfRangeExceptionForInvalidColumn(); - } - - int height = this.array.GetLength(0); - - T[] array = new T[height]; - - ref T r0 = ref array.DangerousGetReference(); - int i = 0; - - // Leverage the enumerator to traverse the column - foreach (T item in this) - { - Unsafe.Add(ref r0, i++) = item; - } - - return array; - } - - /// - /// An enumerator for a source 2D array instance. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public ref struct Enumerator - { -#if SPAN_RUNTIME_SUPPORT - /// - /// The instance mapping the target 2D array. - /// - /// - /// In runtimes where we have support for the type, we can - /// create one from the input 2D array and use that to traverse the target column. - /// This reduces the number of operations to perform for the offsetting to the right - /// column element (we simply need to add to the offset at each - /// iteration to move down by one row), and allows us to use the fast - /// accessor instead of the slower indexer for 2D arrays, as we can then access each - /// individual item linearly, since we know the absolute offset from the base location. - /// - private readonly Span span; - - /// - /// The width of the target 2D array. - /// - private readonly int width; - - /// - /// The current absolute offset within . - /// - private int offset; -#else - /// - /// The source 2D array instance. - /// - private readonly T[,] array; - - /// - /// The target column to iterate within . - /// - private readonly int column; - - /// - /// The height of a column in . - /// - private readonly int height; - - /// - /// The current row. - /// - private int row; -#endif - - /// - /// Initializes a new instance of the struct. - /// - /// The source 2D array instance. - /// The target column to iterate within . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator(T[,] array, int column) - { - if ((uint)column >= (uint)array.GetLength(1)) - { - ThrowArgumentOutOfRangeExceptionForInvalidColumn(); - } - -#if SPAN_RUNTIME_SUPPORT - this.span = array.AsSpan(); - this.width = array.GetLength(1); - this.offset = column - this.width; -#else - this.array = array; - this.column = column; - this.height = array.GetLength(0); - this.row = -1; -#endif - } - - /// - /// Implements the duck-typed method. - /// - /// whether a new element is available, otherwise - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { -#if SPAN_RUNTIME_SUPPORT - int offset = this.offset + this.width; - - if ((uint)offset < (uint)this.span.Length) - { - this.offset = offset; - - return true; - } -#else - int row = this.row + 1; - - if (row < this.height) - { - this.row = row; - - return true; - } -#endif - return false; - } - - /// - /// Gets the duck-typed property. - /// - public ref T Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { -#if SPAN_RUNTIME_SUPPORT - return ref this.span.DangerousGetReferenceAt(this.offset); -#else - return ref this.array[this.row, this.column]; -#endif - } - } - } - - /// - /// Throws an when the is invalid. - /// - private static void ThrowArgumentOutOfRangeExceptionForInvalidColumn() - { - throw new ArgumentOutOfRangeException(nameof(column), "The target column parameter was not valid"); - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs deleted file mode 100644 index 5c146d5a5e8..00000000000 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs +++ /dev/null @@ -1,170 +0,0 @@ -// 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. - -#if !SPAN_RUNTIME_SUPPORT - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics.Contracts; -using System.Runtime.CompilerServices; -using Microsoft.Toolkit.HighPerformance.Extensions; - -namespace Microsoft.Toolkit.HighPerformance.Enumerables -{ - /// - /// A that iterates a row in a given 2D array instance. - /// - /// The type of items to enumerate. - [EditorBrowsable(EditorBrowsableState.Never)] - public readonly ref struct Array2DRowEnumerable - { - /// - /// The source 2D array instance. - /// - private readonly T[,] array; - - /// - /// The target row to iterate within . - /// - private readonly int row; - - /// - /// Initializes a new instance of the struct. - /// - /// The source 2D array instance. - /// The target row to iterate within . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Array2DRowEnumerable(T[,] array, int row) - { - this.array = array; - this.row = row; - } - - /// - /// Implements the duck-typed method. - /// - /// An instance targeting the current 2D array instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator GetEnumerator() => new Enumerator(this.array, this.row); - - /// - /// Returns a array with the values in the target row. - /// - /// A array with the values in the target row. - /// - /// This method will allocate a new array, so only - /// use it if you really need to copy the target items in a new memory location. - /// - [Pure] - public T[] ToArray() - { - if ((uint)row >= (uint)this.array.GetLength(0)) - { - ThrowArgumentOutOfRangeExceptionForInvalidRow(); - } - - int width = this.array.GetLength(1); - - T[] array = new T[width]; - - for (int i = 0; i < width; i++) - { - array.DangerousGetReferenceAt(i) = this.array.DangerousGetReferenceAt(this.row, i); - } - - return array; - } - - /// - /// An enumerator for a source 2D array instance. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public ref struct Enumerator - { - /// - /// The source 2D array instance. - /// - private readonly T[,] array; - - /// - /// The target row to iterate within . - /// - private readonly int row; - - /// - /// The width of a row in . - /// - private readonly int width; - - /// - /// The current column. - /// - private int column; - - /// - /// Initializes a new instance of the struct. - /// - /// The source 2D array instance. - /// The target row to iterate within . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator(T[,] array, int row) - { - if ((uint)row >= (uint)array.GetLength(0)) - { - ThrowArgumentOutOfRangeExceptionForInvalidRow(); - } - - this.array = array; - this.row = row; - this.width = array.GetLength(1); - this.column = -1; - } - - /// - /// Implements the duck-typed method. - /// - /// whether a new element is available, otherwise - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { - int column = this.column + 1; - - if (column < this.width) - { - this.column = column; - - return true; - } - - return false; - } - - /// - /// Gets the duck-typed property. - /// - public ref T Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - // This type is never used on .NET Core runtimes, where - // the fast indexer is available. Therefore, we can just - // use the built-in indexer for 2D arrays to access the value. - return ref this.array[this.row, this.column]; - } - } - } - - /// - /// Throws an when the is invalid. - /// - private static void ThrowArgumentOutOfRangeExceptionForInvalidRow() - { - throw new ArgumentOutOfRangeException(nameof(row), "The target row parameter was not valid"); - } - } -} - -#endif diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 554f66f07d2..20add73535e 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -162,28 +162,11 @@ public static void Fill(this T[,] array, T value, int row, int column, int wi /// 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 . + /// A with the items from the target row within . + /// The returned value shouldn't be used directly: use this extension in a loop. [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) + public static RefEnumerable GetRow(this T[,] array, int row) { if ((uint)row >= (uint)array.GetLength(0)) { @@ -191,11 +174,12 @@ public static } #if SPAN_RUNTIME_SUPPORT + return new RefEnumerable(ref array.DangerousGetReferenceAt(row, 0), array.GetLength(1), 1); +#else ref T r0 = ref array.DangerousGetReferenceAt(row, 0); + IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0); - return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1)); -#else - return new Array2DRowEnumerable(array, row); + return new RefEnumerable(array, offset, array.GetLength(1), 1); #endif } @@ -221,12 +205,24 @@ public static /// 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. + /// 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) + public static RefEnumerable GetColumn(this T[,] array, int column) { - return new Array2DColumnEnumerable(array, column); + if ((uint)column >= (uint)array.GetLength(1)) + { + throw new ArgumentOutOfRangeException(nameof(column)); + } + +#if SPAN_RUNTIME_SUPPORT + return new RefEnumerable(ref array.DangerousGetReferenceAt(0, column), array.GetLength(0), array.GetLength(1)); +#else + ref T r0 = ref array.DangerousGetReferenceAt(0, column); + IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0); + + return new RefEnumerable(array, offset, array.GetLength(0), array.GetLength(1)); +#endif } #if SPAN_RUNTIME_SUPPORT From 5403daa5004d4ed59a4bb62ade9871be3fcb96d8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 15:17:54 +0200 Subject: [PATCH 033/200] Added T[,].GetRowSpan extension --- .../Extensions/ArrayExtensions.2D.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 20add73535e..cc62a78c961 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -227,7 +227,29 @@ public static RefEnumerable GetColumn(this T[,] array, int column) #if SPAN_RUNTIME_SUPPORT /// - /// Creates a new over an input 2D array. + /// 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 . + /// The returned value shouldn't be used directly: use this extension in a loop. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span GetRowSpan(this T[,] array, int row) + { + if ((uint)row >= (uint)array.GetLength(0)) + { + throw new ArgumentOutOfRangeException(nameof(row)); + } + + ref T r0 = ref array.DangerousGetReferenceAt(row, 0); + + return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1)); + } + + /// + /// Cretes a new over an input 2D array. /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. From 97984777712baa8f60620c47c3baae925471d3ea Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 15:25:18 +0200 Subject: [PATCH 034/200] Added Span2D.Get[Row|Column] APIs --- .../Memory/Span2D{T}.Enumerator.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index a9927ef474e..301819e6e70 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; +using Microsoft.Toolkit.HighPerformance.Enumerables; #if SPAN_RUNTIME_SUPPORT using System.Runtime.InteropServices; #else @@ -16,6 +17,48 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// public readonly ref partial struct Span2D { + /// + /// Gets an enumerable that traverses items in a specified row. + /// + /// The target row to enumerate within the current instance. + /// A with target items to enumerate. + /// The returned value shouldn't be used directly: use this extension in a loop. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RefEnumerable GetRow(int row) + { + if ((uint)row >= Height) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + ref T r0 = ref this.DangerousGetReference(); + ref T r1 = ref Unsafe.Add(ref r0, (Width + this.pitch) * row); + + return new RefEnumerable(ref r1, Width, 1); + } + + /// + /// Gets an enumerable that traverses items in a specified column. + /// + /// The target column to enumerate within the current instance. + /// A with target items to enumerate. + /// The returned value shouldn't be used directly: use this extension in a loop. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RefEnumerable GetColumn(int column) + { + if ((uint)column >= Width) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + ref T r0 = ref this.DangerousGetReference(); + ref T r1 = ref Unsafe.Add(ref r0, column); + + return new RefEnumerable(ref r1, Height, Width + this.pitch); + } + /// /// Returns an enumerator for the current instance. /// From 6d194e4ef1b00b765ef7104adb2861accd8b0cd4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 15:30:05 +0200 Subject: [PATCH 035/200] Fixed new APIs on older runtimes --- .../Memory/Span2D{T}.Enumerator.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index 301819e6e70..0a1281574a5 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -35,7 +35,13 @@ public RefEnumerable GetRow(int row) ref T r0 = ref this.DangerousGetReference(); ref T r1 = ref Unsafe.Add(ref r0, (Width + this.pitch) * row); +#if SPAN_RUNTIME_SUPPORT return new RefEnumerable(ref r1, Width, 1); +#else + IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); + + return new RefEnumerable(this.instance!, offset, Width, 1); +#endif } /// @@ -56,7 +62,13 @@ public RefEnumerable GetColumn(int column) ref T r0 = ref this.DangerousGetReference(); ref T r1 = ref Unsafe.Add(ref r0, column); +#if SPAN_RUNTIME_SUPPORT return new RefEnumerable(ref r1, Height, Width + this.pitch); +#else + IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); + + return new RefEnumerable(this.instance!, offset, Height, Width + this.pitch); +#endif } /// From 925fefb80439e78042d60b91949d96440acb37c7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 15:34:20 +0200 Subject: [PATCH 036/200] Minor code refactoring --- .../Memory/{ => Internals}/ThrowHelper.cs | 2 +- Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs | 1 + .../Memory/Span2D{T}.Enumerator.cs | 1 + Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) rename Microsoft.Toolkit.HighPerformance/Memory/{ => Internals}/ThrowHelper.cs (98%) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ThrowHelper.cs b/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs similarity index 98% rename from Microsoft.Toolkit.HighPerformance/Memory/ThrowHelper.cs rename to Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs index 8ebfa96379e..c3bf857a48c 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ThrowHelper.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs @@ -1,6 +1,6 @@ using System; -namespace Microsoft.Toolkit.HighPerformance.Memory +namespace Microsoft.Toolkit.HighPerformance.Memory.Internals { /// /// A helper class to throw exceptions for memory types. diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 0de979d5323..0024e1621d2 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance.Memory.Internals; namespace Microsoft.Toolkit.HighPerformance.Memory { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index 0a1281574a5..208c3bd3e2b 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -6,6 +6,7 @@ using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Enumerables; +using Microsoft.Toolkit.HighPerformance.Memory.Internals; #if SPAN_RUNTIME_SUPPORT using System.Runtime.InteropServices; #else diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index c915985ad6f..b7bbc7751c0 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -8,6 +8,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance.Memory.Internals; namespace Microsoft.Toolkit.HighPerformance.Memory { From a422dbfce5b978c95054679bdff25fc0db3d3359 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 15:39:47 +0200 Subject: [PATCH 037/200] Tweaked some XML docs --- Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 0024e1621d2..f8fe0129328 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -368,7 +368,7 @@ public Span2D Span /// The target column to map within the current instance. /// The width to map within the current instance. /// The height to map within the current instance. - /// + /// /// Thrown when either , or /// are negative or not within the bounds that are valid for the current instance. /// @@ -420,6 +420,7 @@ public unsafe Memory2D Slice(int row, int column, int width, int height) /// /// Copies the contents of this into a destination instance. + /// For this API to succeed, the target has to have the same shape as the current one. /// /// The destination instance. /// @@ -429,6 +430,7 @@ public unsafe Memory2D Slice(int row, int column, int width, int height) /// /// Attempts to copy the current instance to a destination . + /// For this API to succeed, the target has to have the same shape as the current one. /// /// The target of the copy operation. /// Whether or not the operaation was successful. From 57094c24f82d27af0f1d23a730ea210fb79677cd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 15:57:56 +0200 Subject: [PATCH 038/200] Added T[,] array Memory2D extensions --- .../Extensions/ArrayExtensions.2D.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index cc62a78c961..3a553b3a220 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -11,6 +11,7 @@ #endif using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; +using Microsoft.Toolkit.HighPerformance.Memory; namespace Microsoft.Toolkit.HighPerformance.Extensions { @@ -225,6 +226,55 @@ public static RefEnumerable GetColumn(this T[,] array, int column) #endif } + /// + /// Cretes a new over an input 2D array. + /// + /// The type of elements in the input 2D array instance. + /// The input 2D array instance. + /// Thrown when is . + /// A instance with the values of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory2D AsMemory2D(this T[,] array) + { + if (array is null) + { + throw new NullReferenceException(); + } + + return new Memory2D(array); + } + + /// + /// Cretes a new over an input 2D array. + /// + /// The type of elements in the input 2D array instance. + /// The input 2D array instance. + /// The target row to map within . + /// The target column to map within . + /// The width to map within . + /// The height to map within . + /// Thrown when is . + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , or + /// are negative or not within the bounds that are valid for . + /// + /// A instance with the values of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory2D AsMemory2D(this T[,] array, int row, int column, int width, int height) + { + if (array is null) + { + throw new NullReferenceException(); + } + + return new Memory2D(array, row, column, width, height); + } + #if SPAN_RUNTIME_SUPPORT /// /// Returns a over a row in a given 2D array instance. From 025d9c80ea73649cdcf2535f1e550358ccab30e8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 16:16:25 +0200 Subject: [PATCH 039/200] Added T[,,].GetLayer extension --- .../Extensions/ArrayExtensions.2D.cs | 4 +-- .../Extensions/ArrayExtensions.3D.cs | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 3a553b3a220..321c73e46fe 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -227,7 +227,7 @@ public static RefEnumerable GetColumn(this T[,] array, int column) } /// - /// Cretes a new over an input 2D array. + /// Creates a new over an input 2D array. /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. @@ -246,7 +246,7 @@ public static Memory2D AsMemory2D(this T[,] array) } /// - /// Cretes a new over an input 2D array. + /// Creates a new over an input 2D array. /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index a5a5645a192..b64de7dce7a 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -9,6 +9,7 @@ using System.Runtime.InteropServices; #endif using Microsoft.Toolkit.HighPerformance.Helpers.Internals; +using Microsoft.Toolkit.HighPerformance.Memory; namespace Microsoft.Toolkit.HighPerformance.Extensions { @@ -139,6 +140,30 @@ public static Span AsSpan(this T[,,] array) } #endif + /// + /// Creates a new instance of the struct wrapping a layer in a 3D array. + /// + /// The type of elements in the input 3D array instance. + /// The given 3D array to wrap. + /// The target layer to map within . + /// Thrown when is . + /// + /// Thrown when doesn't match . + /// + /// Thrown when either is invalid. + /// A instance wrapping the target layer within . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory2D GetLayer(this T[,,] array, int depth) + { + if (array is null) + { + throw new NullReferenceException(); + } + + return new Memory2D(array, depth); + } + /// /// Counts the number of occurrences of a given value into a target 3D array instance. /// From aef42dbbaba578dc597f07b1e605fc5b97654765 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 16:33:30 +0200 Subject: [PATCH 040/200] Code refactoring to Span2D --- .../Memory/Internals/ThrowHelper.cs | 2 +- .../Memory/Span2D{T}.Enumerator.cs | 8 +- .../Memory/Span2D{T}.cs | 148 ++++++------------ 3 files changed, 49 insertions(+), 109 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs b/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs index c3bf857a48c..17e8629832e 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs @@ -15,6 +15,7 @@ public static void ThrowArgumentExceptionForManagedType() { throw new ArgumentException("Can't create a Span2D from a pointer when T is a managed type"); } +#endif /// /// Throws an when the target span is too short. @@ -23,7 +24,6 @@ public static void ThrowArgumentExceptionForDestinationTooShort() { throw new ArgumentException("The target span is too short to copy all the current items to"); } -#endif /// /// Throws an when using an array of an invalid type. diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index 208c3bd3e2b..a028f7d49c0 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -34,14 +34,14 @@ public RefEnumerable GetRow(int row) } ref T r0 = ref this.DangerousGetReference(); - ref T r1 = ref Unsafe.Add(ref r0, (Width + this.pitch) * row); + ref T r1 = ref Unsafe.Add(ref r0, (this.width + this.pitch) * row); #if SPAN_RUNTIME_SUPPORT return new RefEnumerable(ref r1, Width, 1); #else IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); - return new RefEnumerable(this.instance!, offset, Width, 1); + return new RefEnumerable(this.instance!, offset, this.width, 1); #endif } @@ -64,11 +64,11 @@ public RefEnumerable GetColumn(int column) ref T r1 = ref Unsafe.Add(ref r0, column); #if SPAN_RUNTIME_SUPPORT - return new RefEnumerable(ref r1, Height, Width + this.pitch); + return new RefEnumerable(ref r1, Height, this.width + this.pitch); #else IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); - return new RefEnumerable(this.instance!, offset, Height, Width + this.pitch); + return new RefEnumerable(this.instance!, offset, Height, this.width + this.pitch); #endif } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index b7bbc7751c0..e7e9ed32d9d 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -321,34 +321,26 @@ public int Width /// public void Clear() { -#if SPAN_RUNTIME_SUPPORT - if (this.pitch == 0) + if (TryGetSpan(out Span span)) { - // If the pitch is 0, it means all the target area is contiguous - // in memory with no padding between row boundaries. In this case - // we can just create a Span over the area and use it to clear it. - MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Size).Clear(); + span.Clear(); } else { - ref T r0 = ref MemoryMarshal.GetReference(this.span); - IntPtr step = (IntPtr)(this.width + this.pitch); - - // Clear each row individually, as they're not contiguous - for (int i = 0; i < this.span.Length; i++) +#if SPAN_RUNTIME_SUPPORT + // Clear one row at a time + for (int i = 0; i < Height; i++) { - MemoryMarshal.CreateSpan(ref r0, this.width).Clear(); - - r0 = ref Unsafe.Add(ref r0, step); + GetRowSpan(i).Clear(); } - } #else - // Fallback to the enumerator to traverse the span - foreach (ref T item in this) - { - item = default!; - } + // Fallback to the enumerator + foreach (ref T item in this) + { + item = default!; + } #endif + } } /// @@ -360,11 +352,9 @@ public void Clear() /// public void CopyTo(Span destination) { -#if SPAN_RUNTIME_SUPPORT - if (this.pitch == 0) + if (TryGetSpan(out Span span)) { - // If the pitch is 0, we can copy in a single pass - MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Size).CopyTo(destination); + span.CopyTo(destination); } else { @@ -373,31 +363,25 @@ public void CopyTo(Span destination) ThrowHelper.ThrowArgumentExceptionForDestinationTooShort(); } - ref T sourceRef = ref MemoryMarshal.GetReference(this.span); - IntPtr step = (IntPtr)(this.width + this.pitch); - int offset = 0; - +#if SPAN_RUNTIME_SUPPORT // Copy each row individually - for (int i = 0; i < this.span.Length; i++) + for (int i = 0, j = 0; i < Height; i++, j += Width) { - MemoryMarshal.CreateSpan(ref sourceRef, this.width).CopyTo(destination.Slice(offset)); - - sourceRef = ref Unsafe.Add(ref sourceRef, step); - offset += this.width; + GetRowSpan(i).CopyTo(destination.Slice(j)); } - } #else - // Similar to the previous case - ref T destinationRef = ref MemoryMarshal.GetReference(destination); - IntPtr offset = default; + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + IntPtr offset = default; - foreach (T item in this) - { - Unsafe.Add(ref destinationRef, offset) = item; + // Fallback to the enumerator again + foreach (T item in this) + { + Unsafe.Add(ref destinationRef, offset) = item; - offset += 1; - } + offset += 1; + } #endif + } } /// @@ -423,23 +407,10 @@ public void CopyTo(Span2D destination) else { #if SPAN_RUNTIME_SUPPORT - int - sourcePaddedWitdh = this.width + this.pitch, - destinationPaddedWidth = destination.width + destination.pitch, - sourcePaddedSize = sourcePaddedWitdh * Height, - destinationPaddedSize = destinationPaddedWidth * Height, - sourceOffset = 0, - destinationOffset = 0; - Span - fullSourceSpan = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), sourcePaddedSize), - fullDestinationSpan = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(destination.span), destinationPaddedSize); - + // Copy each row individually for (int i = 0; i < Height; i++) { - fullSourceSpan.Slice(sourceOffset, this.width).CopyTo(fullDestinationSpan.Slice(destinationOffset, this.width)); - - sourceOffset += sourcePaddedSize; - destinationOffset += destinationPaddedSize; + GetRowSpan(i).CopyTo(destination.GetRowSpan(i)); } #else Enumerator destinationEnumerator = destination.GetEnumerator(); @@ -496,31 +467,26 @@ public bool TryCopyTo(Span2D destination) /// The value to assign to each element of the instance. public void Fill(T value) { -#if SPAN_RUNTIME_SUPPORT - if (this.pitch == 0) + if (TryGetSpan(out Span span)) { - MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Size).Fill(value); + span.Fill(value); } else { - ref T sourceRef = ref MemoryMarshal.GetReference(this.span); - IntPtr step = (IntPtr)(this.width + this.pitch); - - // Fill each row individually - for (int i = 0; i < this.span.Length; i++) +#if SPAN_RUNTIME_SUPPORT + // Fill one row at a time + for (int i = 0; i < Height; i++) { - MemoryMarshal.CreateSpan(ref sourceRef, this.width).Fill(value); - - sourceRef = ref Unsafe.Add(ref sourceRef, step); + GetRowSpan(i).Fill(value); } - } #else - // Use the enumerator again - foreach (ref T item in this) - { - item = value; - } + // Fill using the enumerator as above + foreach (ref T item in this) + { + item = value; + } #endif + } } /// @@ -679,31 +645,11 @@ public bool TryGetSpan(out Span span) [Pure] public T[,] ToArray() { -#if SPAN_RUNTIME_SUPPORT - T[,] array = new T[this.span.Length, this.width]; - - if (this.pitch == 0) - { - CopyTo(array.AsSpan()); - } - else - { - ref T sourceRef = ref MemoryMarshal.GetReference(this.span); - IntPtr step = (IntPtr)(this.width + this.pitch); - int offset = 0; + T[,] array = new T[Height, this.width]; - // Copy each row individually - for (int i = 0; i < this.span.Length; i++) - { - MemoryMarshal.CreateSpan(ref sourceRef, this.width).CopyTo(array.AsSpan().Slice(offset)); - - sourceRef = ref Unsafe.Add(ref sourceRef, step); - offset += this.width; - } - } +#if SPAN_RUNTIME_SUPPORT + CopyTo(array.AsSpan()); #else - T[,] array = new T[this.height, this.width]; - // Skip the initialization if the array is empty if (Size > 0) { @@ -744,13 +690,7 @@ public override int GetHashCode() /// public override string ToString() { -#if SPAN_RUNTIME_SUPPORT - int height = this.span.Length; -#else - int height = this.height; -#endif - - return $"Microsoft.Toolkit.HighPerformance.Memory.Span2D<{typeof(T)}>[{height}, {this.width}]"; + return $"Microsoft.Toolkit.HighPerformance.Memory.Span2D<{typeof(T)}>[{Height}, {this.width}]"; } /// From 1aabc38f84fd1105f07544ffc983439b19c93264 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 16:54:08 +0200 Subject: [PATCH 041/200] Added missing Span2D constructors --- .../Memory/Memory2D{T}.cs | 4 +- .../Memory/Span2D{T}.cs | 104 +++++++++++++++++- 2 files changed, 103 insertions(+), 5 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index f8fe0129328..0187edff285 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -148,7 +148,7 @@ public Memory2D(T[,]? array) /// /// Thrown when doesn't match . /// - /// + /// /// Thrown when either , or /// are negative or not within the bounds that are valid for . /// @@ -211,7 +211,7 @@ public Memory2D(T[,]? array, int row, int column, int width, int height) /// /// Thrown when doesn't match . /// - /// Thrown when either is invalid. + /// Thrown when either is invalid. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(T[,,] array, int depth) { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index e7e9ed32d9d..0eefe48c94d 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -148,6 +148,71 @@ internal Span2D(object instance, IntPtr offset, int height, int width, int pitch } #endif + /// + /// Initializes a new instance of the struct. + /// + /// The target array to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span2D(T[] array, int offset, int width, int height) + : this(array, offset, width, height, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target array to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// The pitch in the resulting 2D area. + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , , + /// or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span2D(T[] array, int offset, int width, int height, int pitch) + { + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } + + if ((uint)offset >= (uint)array.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); + } + + int remaining = array.Length - offset; + + if ((((uint)width + (uint)pitch) * (uint)height) > (uint)remaining) + { + ThrowHelper.ThrowArgumentException(); + } + +#if SPAN_RUNTIME_SUPPORT + this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(offset), height); +#else + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)); + this.height = height; +#endif + this.width = width; + this.pitch = pitch; + } + /// /// Initializes a new instance of the struct wrapping a 2D array. /// @@ -170,7 +235,7 @@ public Span2D(T[,]? array) } #if SPAN_RUNTIME_SUPPORT - this.span = MemoryMarshal.CreateSpan(ref array[0, 0], array.GetLength(0)); + this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReference(), array.GetLength(0)); #else this.instance = array; this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(0, 0)); @@ -191,7 +256,7 @@ public Span2D(T[,]? array) /// /// Thrown when doesn't match . /// - /// + /// /// Thrown when either , or /// are negative or not within the bounds that are valid for . /// @@ -227,7 +292,7 @@ public Span2D(T[,] array, int row, int column, int width, int height) } #if SPAN_RUNTIME_SUPPORT - this.span = MemoryMarshal.CreateSpan(ref array[row, column], height); + this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(row, column), height); #else this.instance = array; this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column)); @@ -237,6 +302,39 @@ public Span2D(T[,] array, int row, int column, int width, int height) this.pitch = row + (array.GetLength(1) - column); } + /// + /// Initializes a new instance of the struct wrapping a layer in a 3D array. + /// + /// The given 3D array to wrap. + /// The target layer to map within . + /// + /// Thrown when doesn't match . + /// + /// Thrown when either is invalid. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span2D(T[,,] array, int depth) + { + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } + + if ((uint)depth >= (uint)array.GetLength(0)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); + } + +#if SPAN_RUNTIME_SUPPORT + this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(depth, 0, 0), array.GetLength(1)); +#else + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0)); + this.height = array.GetLength(1); +#endif + this.width = array.GetLength(2); + this.pitch = 0; + } + /// /// Gets an empty instance. /// From 0cb7ec49bee65c49ac1aca2bc42dfb5da6f84f5b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 16:54:54 +0200 Subject: [PATCH 042/200] Added new Span[2D] APIs for 2D/3D arrays --- .../Extensions/ArrayExtensions.2D.cs | 49 +++++++++++++++ .../Extensions/ArrayExtensions.3D.cs | 60 ++++++++++++++++++- 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 321c73e46fe..434a5fa39af 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -226,6 +226,55 @@ public static RefEnumerable GetColumn(this T[,] array, int column) #endif } + /// + /// Creates a new over an input 2D array. + /// + /// The type of elements in the input 2D array instance. + /// The input 2D array instance. + /// Thrown when is . + /// A instance with the values of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span2D AsSpan2D(this T[,] array) + { + if (array is null) + { + throw new NullReferenceException(); + } + + return new Span2D(array); + } + + /// + /// Creates a new over an input 2D array. + /// + /// The type of elements in the input 2D array instance. + /// The input 2D array instance. + /// The target row to map within . + /// The target column to map within . + /// The width to map within . + /// The height to map within . + /// Thrown when is . + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , or + /// are negative or not within the bounds that are valid for . + /// + /// A instance with the values of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span2D AsSpan2D(this T[,] array, int row, int column, int width, int height) + { + if (array is null) + { + throw new NullReferenceException(); + } + + return new Span2D(array, row, column, width, height); + } + /// /// Creates a new over an input 2D array. /// diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index b64de7dce7a..c101ca8d166 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -140,6 +140,64 @@ public static Span AsSpan(this T[,,] array) } #endif +#if SPAN_RUNTIME_SUPPORT + /// + /// Creates a new instance of the struct wrapping a layer in a 3D array. + /// + /// The type of elements in the input 3D array instance. + /// The given 3D array to wrap. + /// The target layer to map within . + /// Thrown when is . + /// + /// Thrown when doesn't match . + /// + /// Thrown when either is invalid. + /// A instance wrapping the target layer within . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span GetLayerSpan(this T[,,] array, int depth) + { + if (array is null) + { + throw new NullReferenceException(); + } + + if ((uint)depth >= (uint)array.GetLength(0)) + { + throw new ArgumentOutOfRangeException(nameof(depth)); + } + + ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0); + int length = array.GetLength(1) * array.GetLength(2); + + return MemoryMarshal.CreateSpan(ref r0, length); + } +#endif + + /// + /// Creates a new instance of the struct wrapping a layer in a 3D array. + /// + /// The type of elements in the input 3D array instance. + /// The given 3D array to wrap. + /// The target layer to map within . + /// Thrown when is . + /// + /// Thrown when doesn't match . + /// + /// Thrown when either is invalid. + /// A instance wrapping the target layer within . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span2D GetLayerSpan2D(this T[,,] array, int depth) + { + if (array is null) + { + throw new NullReferenceException(); + } + + return new Span2D(array, depth); + } + /// /// Creates a new instance of the struct wrapping a layer in a 3D array. /// @@ -154,7 +212,7 @@ public static Span AsSpan(this T[,,] array) /// A instance wrapping the target layer within . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Memory2D GetLayer(this T[,,] array, int depth) + public static Memory2D GetLayerMemory2D(this T[,,] array, int depth) { if (array is null) { From 5735f91f2195fc4e440cac7d4d87c0065d34ad6b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 17:23:56 +0200 Subject: [PATCH 043/200] Added ReadOnly[Memory|Span]2D types --- .../Memory/ReadOnlyMemory2D{T}.cs | 605 ++++++++++++++ .../Memory/ReadOnlySpan2D{T}.Enumerator.cs | 208 +++++ .../Memory/ReadOnlySpan2D{T}.cs | 747 ++++++++++++++++++ 3 files changed, 1560 insertions(+) create mode 100644 Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs create mode 100644 Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs create mode 100644 Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs new file mode 100644 index 00000000000..7ace6a0df9e --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -0,0 +1,605 @@ +// 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.Buffers; +using System.ComponentModel; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance.Memory.Internals; + +namespace Microsoft.Toolkit.HighPerformance.Memory +{ + /// + /// A readonly version of . + /// + /// The type of items in the current instance. + public readonly struct ReadOnlyMemory2D : IEquatable> + { + /// + /// The target instance, if present. + /// + private readonly object? instance; + + /// + /// The initial offset within . + /// + private readonly IntPtr offset; + + /// + /// The height of the specified 2D region. + /// + private readonly int height; + + /// + /// The width of the specified 2D region. + /// + private readonly int width; + + /// + /// The pitch of the specified 2D region. + /// + private readonly int pitch; + + /// + /// Initializes a new instance of the struct. + /// + /// The target array to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory2D(T[] array, int offset, int width, int height) + : this(array, offset, width, height, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target array to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// The pitch in the resulting 2D area. + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , , + /// or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory2D(T[] array, int offset, int width, int height, int pitch) + { + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } + + if ((uint)offset >= (uint)array.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); + } + + int remaining = array.Length - offset; + + if ((((uint)width + (uint)pitch) * (uint)height) > (uint)remaining) + { + ThrowHelper.ThrowArgumentException(); + } + + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)); + this.height = height; + this.width = width; + this.pitch = pitch; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target array to wrap. + /// + /// Thrown when doesn't match . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory2D(T[,]? array) + { + if (array is null) + { + this = default; + + return; + } + + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } + + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReference()); + this.height = array.GetLength(0); + this.width = array.GetLength(1); + this.pitch = 0; + } + + /// + /// Initializes a new instance of the struct wrapping a 2D array. + /// + /// The given 2D array to wrap. + /// The target row to map within . + /// The target column to map within . + /// The width to map within . + /// The height to map within . + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , or + /// are negative or not within the bounds that are valid for . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory2D(T[,]? array, int row, int column, int width, int height) + { + if (array is null) + { + if ((row | column | width | height) != 0) + { + ThrowHelper.ThrowArgumentException(); + } + + this = default; + + return; + } + + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } + + int + rows = array.GetLength(0), + columns = array.GetLength(1); + + if ((uint)row >= (uint)rows) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + if ((uint)column >= (uint)columns) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + if (width > (columns - column)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (height > (rows - row)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column)); + this.height = height; + this.width = width; + this.pitch = row + (array.GetLength(1) - column); + } + + /// + /// Initializes a new instance of the struct wrapping a layer in a 3D array. + /// + /// The given 3D array to wrap. + /// The target layer to map within . + /// + /// Thrown when doesn't match . + /// + /// Thrown when either is invalid. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory2D(T[,,] array, int depth) + { + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } + + if ((uint)depth >= (uint)array.GetLength(0)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); + } + + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0)); + this.height = array.GetLength(1); + this.width = array.GetLength(2); + this.pitch = 0; + } + +#if SPAN_RUNTIME_SUPPORT + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// + /// Thrown when either , or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory2D(Memory memory, int offset, int width, int height) + : this((ReadOnlyMemory)memory, offset, width, height, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// The pitch in the resulting 2D area. + /// + /// Thrown when either , , + /// or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory2D(Memory memory, int offset, int width, int height, int pitch) + : this((ReadOnlyMemory)memory, offset, width, height, pitch) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// + /// Thrown when either , or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int width, int height) + : this(memory, offset, width, height, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// The pitch in the resulting 2D area. + /// + /// Thrown when either , , + /// or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int width, int height, int pitch) + { + if ((uint)offset >= (uint)memory.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); + } + + int remaining = memory.Length - offset; + + if ((((uint)width + (uint)pitch) * (uint)height) > (uint)remaining) + { + ThrowHelper.ThrowArgumentException(); + } + + this.instance = memory.Slice(offset); + this.offset = default; + this.height = height; + this.width = width; + this.pitch = pitch; + } +#endif + + /// + /// Initializes a new instance of the struct with the specified parameters. + /// + /// The target instance. + /// The initial offset within . + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ReadOnlyMemory2D(object instance, IntPtr offset, int height, int width, int pitch) + { + this.instance = instance; + this.offset = offset; + this.height = height; + this.width = width; + this.pitch = pitch; + } + + /// + /// Gets an empty instance. + /// + public static ReadOnlyMemory2D Empty => default; + + /// + /// Gets a value indicating whether the current instance is empty. + /// + public bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (this.height | this.width) == 0; + } + + /// + /// Gets the length of the current instance. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.height * this.width; + } + + /// + /// Gets a instance from the current memory. + /// + public ReadOnlySpan2D Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (!(this.instance is null)) + { +#if SPAN_RUNTIME_SUPPORT + if (this.instance.GetType() == typeof(ReadOnlyMemory)) + { + ReadOnlyMemory memory = (ReadOnlyMemory)this.instance; + + // If the wrapped object is a ReadOnlyMemory, it is always pre-offset + ref T r0 = ref memory.Span.DangerousGetReference(); + + return new ReadOnlySpan2D(ref r0, this.height, this.width, this.pitch); + } + else + { + // The only other possible cases is with the instance being an array + ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset); + + return new ReadOnlySpan2D(ref r0, this.height, this.width, this.pitch); + } +#else + return new ReadOnlySpan2D(this.instance, this.offset, this.height, this.width, this.pitch); +#endif + } + + return default; + } + } + + /// + /// Slices the current instance with the specified parameters. + /// + /// The target row to map within the current instance. + /// The target column to map within the current instance. + /// The width to map within the current instance. + /// The height to map within the current instance. + /// + /// Thrown when either , or + /// are negative or not within the bounds that are valid for the current instance. + /// + /// A new instance representing a slice of the current one. + [Pure] + public unsafe ReadOnlyMemory2D Slice(int row, int column, int width, int height) + { + if ((uint)row >= this.height) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + if ((uint)column >= this.width) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + if ((uint)width > (this.width - column)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if ((uint)height > (this.height - row)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + int shift = ((this.width + this.pitch) * row) + column; + IntPtr offset = (IntPtr)((byte*)this.offset + shift); + + return new ReadOnlyMemory2D(this.instance!, offset, height, width, this.pitch); + } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(Memory destination) => Span.CopyTo(destination.Span); + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operaation was successful. + public bool TryCopyTo(Memory destination) => Span.TryCopyTo(destination.Span); + + /// + /// Copies the contents of this into a destination instance. + /// For this API to succeed, the target has to have the same shape as the current one. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(Memory2D destination) => Span.CopyTo(destination.Span); + + /// + /// Attempts to copy the current instance to a destination . + /// For this API to succeed, the target has to have the same shape as the current one. + /// + /// The target of the copy operation. + /// Whether or not the operaation was successful. + public bool TryCopyTo(Memory2D destination) => Span.TryCopyTo(destination.Span); + + /// + /// Creates a handle for the memory. + /// The GC will not move the memory until the returned + /// is disposed, enabling taking and using the memory's address. + /// + /// + /// An instance with nonprimitive (non-blittable) members cannot be pinned. + /// + /// A instance wrapping the pinned handle. + public unsafe MemoryHandle Pin() + { + if (!(this.instance is null)) + { + GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned); + + void* pointer = Unsafe.AsPointer(ref this.instance.DangerousGetObjectDataReferenceAt(this.offset)); + + return new MemoryHandle(pointer, handle); + } + + return default; + } + + /// + /// Tries to get a instance, if the underlying buffer is contiguous. + /// + /// The resulting , in case of success. + /// Whether or not was correctly assigned. + public bool TryGetMemory(out ReadOnlyMemory memory) + { + if (this.pitch == 0) + { + // Empty Memory2D instance + if (this.instance is null) + { + memory = default; + } + else if (this.instance.GetType() == typeof(ReadOnlyMemory)) + { + // If the object is a ReadOnlyMemory, just slice it as needed + memory = ((ReadOnlyMemory)this.instance).Slice(0, this.height * this.width); + } + else if (this.instance.GetType() == typeof(T[])) + { + // If it's a T[] array, also handle the initial offset + memory = new ReadOnlyMemory(Unsafe.As(this.instance), (int)this.offset, this.height * this.width); + } + else + { + // Reuse a single failure path to reduce + // the number of returns in the method + goto Failure; + } + + return true; + } + + Failure: + + memory = default; + + return false; + } + + /// + /// Copies the contents of the current instance into a new 2D array. + /// + /// A 2D array containing the data in the current instance. + [Pure] + public T[,] ToArray() => Span.ToArray(); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) + { + return obj is ReadOnlyMemory2D memory && Equals(memory); + } + + /// + public bool Equals(ReadOnlyMemory2D other) + { + return + this.instance == other.instance && + this.offset == other.offset && + this.height == other.height && + this.width == other.width && + this.pitch == other.pitch; + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + if (!(this.instance is null)) + { +#if SPAN_RUNTIME_SUPPORT + return HashCode.Combine( + RuntimeHelpers.GetHashCode(this.instance), + this.offset, + this.height, + this.width, + this.pitch); +#else + Span values = stackalloc int[] + { + RuntimeHelpers.GetHashCode(this.instance), + this.offset.GetHashCode(), + this.height, + this.width, + this.pitch + }; + + return values.GetDjb2HashCode(); +#endif + } + + return 0; + } + + /// + public override string ToString() + { + return $"Microsoft.Toolkit.HighPerformance.Memory.ReadOnlyMemory2D<{typeof(T)}>[{this.height}, {this.width}]"; + } + + /// + /// Defines an implicit conversion of an array to a + /// + public static implicit operator ReadOnlyMemory2D(T[,]? array) => new ReadOnlyMemory2D(array); + + /// + /// Defines an implicit conversion of a to a + /// + public static implicit operator ReadOnlyMemory2D(Memory2D memory) => Unsafe.As, ReadOnlyMemory2D>(ref memory); + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs new file mode 100644 index 00000000000..bd66f7fff7c --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs @@ -0,0 +1,208 @@ +// 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 Microsoft.Toolkit.HighPerformance.Enumerables; +using Microsoft.Toolkit.HighPerformance.Memory.Internals; +#if SPAN_RUNTIME_SUPPORT +using System.Runtime.InteropServices; +#else +using Microsoft.Toolkit.HighPerformance.Extensions; +#endif + +namespace Microsoft.Toolkit.HighPerformance.Memory +{ + /// + public readonly ref partial struct ReadOnlySpan2D + { + /// + /// Gets an enumerable that traverses items in a specified row. + /// + /// The target row to enumerate within the current instance. + /// A with target items to enumerate. + /// The returned value shouldn't be used directly: use this extension in a loop. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyRefEnumerable GetRow(int row) + { + if ((uint)row >= Height) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + ref T r0 = ref this.DangerousGetReference(); + ref T r1 = ref Unsafe.Add(ref r0, (this.width + this.pitch) * row); + +#if SPAN_RUNTIME_SUPPORT + return new ReadOnlyRefEnumerable(ref r1, Width, 1); +#else + IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); + + return new ReadOnlyRefEnumerable(this.instance!, offset, this.width, 1); +#endif + } + + /// + /// Gets an enumerable that traverses items in a specified column. + /// + /// The target column to enumerate within the current instance. + /// A with target items to enumerate. + /// The returned value shouldn't be used directly: use this extension in a loop. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyRefEnumerable GetColumn(int column) + { + if ((uint)column >= Width) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + ref T r0 = ref this.DangerousGetReference(); + ref T r1 = ref Unsafe.Add(ref r0, column); + +#if SPAN_RUNTIME_SUPPORT + return new ReadOnlyRefEnumerable(ref r1, Height, this.width + this.pitch); +#else + IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); + + return new ReadOnlyRefEnumerable(this.instance!, offset, Height, this.width + this.pitch); +#endif + } + + /// + /// Returns an enumerator for the current instance. + /// + /// + /// An enumerator that can be used to traverse the items in the current instance + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new Enumerator(this); + + /// + /// Provides an enumerator for the elements of a instance. + /// + public ref struct Enumerator + { +#if SPAN_RUNTIME_SUPPORT + /// + /// The instance pointing to the first item in the target memory area. + /// + /// Just like in , the length is the height of the 2D region. + private readonly ReadOnlySpan span; +#else + /// + /// The target instance, if present. + /// + private readonly object? instance; + + /// + /// The initial offset within . + /// + private readonly IntPtr offset; + + /// + /// The height of the specified 2D region. + /// + private readonly int height; +#endif + + /// + /// The width of the specified 2D region. + /// + private readonly int width; + + /// + /// The pitch of the specified 2D region. + /// + private readonly int pitch; + + /// + /// The current horizontal offset. + /// + private int x; + + /// + /// The current vertical offset. + /// + private int y; + + /// + /// Initializes a new instance of the struct. + /// + /// The target instance to enumerate. + internal Enumerator(ReadOnlySpan2D span) + { +#if SPAN_RUNTIME_SUPPORT + this.span = span.span; +#else + this.instance = span.instance; + this.offset = span.offset; + this.height = span.height; +#endif + this.width = span.width; + this.pitch = span.pitch; + this.x = -1; + this.y = 0; + } + + /// + /// Implements the duck-typed method. + /// + /// whether a new element is available, otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + int x = this.x + 1; + + // Horizontal move, within range + if (x < this.width) + { + this.x = x; + + return true; + } + + // We reached the end of a row and there is at least + // another row available: wrap to a new line and continue. + if ( +#if SPAN_RUNTIME_SUPPORT + this.y < (this.span.Length - 1) +#else + this.y < this.height - 1 +#endif + ) + { + this.x = 0; + this.y++; + + return true; + } + + return false; + } + + /// + /// Gets the duck-typed property. + /// + public ref readonly T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref MemoryMarshal.GetReference(this.span); +#else + ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); +#endif + int index = (this.y * (this.width + this.pitch)) + this.x; + + return ref Unsafe.Add(ref r0, index); + } + } + } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs new file mode 100644 index 00000000000..588745fad31 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -0,0 +1,747 @@ +// 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.ComponentModel; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance.Memory.Internals; + +namespace Microsoft.Toolkit.HighPerformance.Memory +{ + /// + /// A readonly version of . + /// + /// The type of items in the current instance. + public readonly ref partial struct ReadOnlySpan2D + { +#if SPAN_RUNTIME_SUPPORT + /// + /// The instance pointing to the first item in the target memory area. + /// + private readonly ReadOnlySpan span; +#else + /// + /// The target instance, if present. + /// + private readonly object? instance; + + /// + /// The initial offset within . + /// + private readonly IntPtr offset; + + /// + /// The height of the specified 2D region. + /// + private readonly int height; +#endif + + /// + /// The width of the specified 2D region. + /// + private readonly int width; + + /// + /// The pitch of the specified 2D region. + /// + private readonly int pitch; + +#if SPAN_RUNTIME_SUPPORT + /// + /// Initializes a new instance of the struct with the specified parameters. + /// + /// The reference to the first item to map. + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map (the distance between each row). + /// + /// Thrown when either , or are negative. + /// + public ReadOnlySpan2D(ref T value, int height, int width, int pitch) + { + if ((height | width | pitch) < 0) + { + ThrowHelper.ThrowArgumentException(); + } + + this.span = MemoryMarshal.CreateReadOnlySpan(ref value, height); + this.width = width; + this.pitch = pitch; + } + + /// + /// Initializes a new instance of the struct with the specified parameters. + /// + /// The pointer to the first item to map. + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map (the distance between each row). + /// + /// Thrown when is a reference type or contains references. Also thrown + /// when either , or are negative. + /// + public unsafe ReadOnlySpan2D(void* pointer, int height, int width, int pitch) + { + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + ThrowHelper.ThrowArgumentExceptionForManagedType(); + } + + if ((height | width | pitch) < 0) + { + ThrowHelper.ThrowArgumentException(); + } + + this.span = new Span(pointer, height); + this.width = width; + this.pitch = pitch; + } +#else + /// + /// Initializes a new instance of the struct with the specified parameters. + /// + /// The target instance. + /// The initial offset within . + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map. + internal ReadOnlySpan2D(object instance, IntPtr offset, int height, int width, int pitch) + { + this.instance = instance; + this.offset = offset; + this.height = height; + this.width = width; + this.pitch = pitch; + } +#endif + + /// + /// Initializes a new instance of the struct. + /// + /// The target array to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan2D(T[] array, int offset, int width, int height) + : this(array, offset, width, height, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target array to wrap. + /// The initial offset within . + /// The width of each row in the resulting 2D area. + /// The height of the resulting 2D area. + /// The pitch in the resulting 2D area. + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , , + /// or are invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan2D(T[] array, int offset, int width, int height, int pitch) + { + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } + + if ((uint)offset >= (uint)array.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); + } + + int remaining = array.Length - offset; + + if ((((uint)width + (uint)pitch) * (uint)height) > (uint)remaining) + { + ThrowHelper.ThrowArgumentException(); + } + +#if SPAN_RUNTIME_SUPPORT + this.span = MemoryMarshal.CreateReadOnlySpan(ref array.DangerousGetReferenceAt(offset), height); +#else + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)); + this.height = height; +#endif + this.width = width; + this.pitch = pitch; + } + + /// + /// Initializes a new instance of the struct wrapping a 2D array. + /// + /// The given 2D array to wrap. + /// + /// Thrown when doesn't match . + /// + public ReadOnlySpan2D(T[,]? array) + { + if (array is null) + { + this = default; + + return; + } + + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } + +#if SPAN_RUNTIME_SUPPORT + this.span = MemoryMarshal.CreateReadOnlySpan(ref array.DangerousGetReference(), array.GetLength(0)); +#else + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(0, 0)); + this.height = array.GetLength(0); +#endif + this.width = array.GetLength(1); + this.pitch = 0; + } + + /// + /// Initializes a new instance of the struct wrapping a 2D array. + /// + /// The given 2D array to wrap. + /// The target row to map within . + /// The target column to map within . + /// The width to map within . + /// The height to map within . + /// + /// Thrown when doesn't match . + /// + /// + /// Thrown when either , or + /// are negative or not within the bounds that are valid for . + /// + public ReadOnlySpan2D(T[,] array, int row, int column, int width, int height) + { + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } + + int + rows = array.GetLength(0), + columns = array.GetLength(1); + + if ((uint)row >= (uint)rows) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + if ((uint)column >= (uint)columns) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + if (width > (columns - column)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (height > (rows - row)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + +#if SPAN_RUNTIME_SUPPORT + this.span = MemoryMarshal.CreateReadOnlySpan(ref array.DangerousGetReferenceAt(row, column), height); +#else + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column)); + this.height = array.GetLength(0); +#endif + this.width = width; + this.pitch = row + (array.GetLength(1) - column); + } + + /// + /// Initializes a new instance of the struct wrapping a layer in a 3D array. + /// + /// The given 3D array to wrap. + /// The target layer to map within . + /// + /// Thrown when doesn't match . + /// + /// Thrown when either is invalid. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan2D(T[,,] array, int depth) + { + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } + + if ((uint)depth >= (uint)array.GetLength(0)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); + } + +#if SPAN_RUNTIME_SUPPORT + this.span = MemoryMarshal.CreateReadOnlySpan(ref array.DangerousGetReferenceAt(depth, 0, 0), array.GetLength(1)); +#else + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0)); + this.height = array.GetLength(1); +#endif + this.width = array.GetLength(2); + this.pitch = 0; + } + + /// + /// Gets an empty instance. + /// + public static ReadOnlySpan2D Empty => default; + + /// + /// Gets a value indicating whether the current instance is empty. + /// + public bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (Height | Width) == 0; + } + + /// + /// Gets the length of the current instance. + /// + public int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Height * Width; + } + + /// + /// Gets the height of the underlying 2D memory area. + /// + public int Height + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { +#if SPAN_RUNTIME_SUPPORT + return this.span.Length; +#else + return this.height; +#endif + } + } + + /// + /// Gets the width of the underlying 2D memory area. + /// + public int Width + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.width; + } + + /// + /// Gets the element at the specified zero-based indices. + /// + /// The target row to get the element from. + /// The target column to get the element from. + /// A reference to the element at the specified indices. + /// + /// Thrown when either or are invalid. + /// + public ref readonly T this[int i, int j] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((uint)i >= (uint)Height || + (uint)j >= (uint)Width) + { + ThrowHelper.ThrowIndexOutOfRangeException(); + } + +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref MemoryMarshal.GetReference(this.span); +#else + ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); +#endif + int index = (i * (this.width + this.pitch)) + j; + + return ref Unsafe.Add(ref r0, index); + } + } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(Span destination) + { + if (TryGetSpan(out ReadOnlySpan span)) + { + span.CopyTo(destination); + } + else + { + if (Size > destination.Length) + { + ThrowHelper.ThrowArgumentExceptionForDestinationTooShort(); + } + +#if SPAN_RUNTIME_SUPPORT + // Copy each row individually + for (int i = 0, j = 0; i < Height; i++, j += Width) + { + GetRowSpan(i).CopyTo(destination.Slice(j)); + } +#else + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + IntPtr offset = default; + + // Fallback to the enumerator again + foreach (T item in this) + { + Unsafe.Add(ref destinationRef, offset) = item; + + offset += 1; + } +#endif + } + } + + /// + /// Copies the contents of this into a destination instance. + /// For this API to succeed, the target has to have the same shape as the current one. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(Span2D destination) + { + if (destination.Height != Height || + destination.Width != Width) + { + ThrowHelper.ThrowArgumentException(); + } + + if (destination.TryGetSpan(out Span span)) + { + CopyTo(span); + } + else + { +#if SPAN_RUNTIME_SUPPORT + // Copy each row individually + for (int i = 0; i < Height; i++) + { + GetRowSpan(i).CopyTo(destination.GetRowSpan(i)); + } +#else + Span2D.Enumerator destinationEnumerator = destination.GetEnumerator(); + + // Fallback path with two enumerators + foreach (T item in this) + { + _ = destinationEnumerator.MoveNext(); + + destinationEnumerator.Current = item; + } +#endif + } + } + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operation was successful. + public bool TryCopyTo(Span destination) + { + if (destination.Length >= Size) + { + CopyTo(destination); + + return true; + } + + return false; + } + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operation was successful. + public bool TryCopyTo(Span2D destination) + { + if (destination.Height == Height && + destination.Width == Width) + { + CopyTo(destination); + + return true; + } + + return false; + } + + /// + /// Returns a reference to the 0th element of the instance. If the current + /// instance is empty, returns a reference. It can be used for pinning + /// and is required to support the use of span within a fixed statement. + /// + /// A reference to the 0th element, or a reference. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [EditorBrowsable(EditorBrowsableState.Never)] + public unsafe ref T GetPinnableReference() + { + ref T r0 = ref Unsafe.AsRef(null); + + if (Size != 0) + { + r0 = ref this.DangerousGetReference(); + } + + return ref r0; + } + + /// + /// Returns a reference to the first element within the current instance, with no bounds check. + /// + /// A reference to the first element within the current instance. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T DangerousGetReference() + { +#if SPAN_RUNTIME_SUPPORT + return ref MemoryMarshal.GetReference(this.span); +#else + return ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); +#endif + } + + /// + /// Slices the current instance with the specified parameters. + /// + /// The target row to map within the current instance. + /// The target column to map within the current instance. + /// The width to map within the current instance. + /// The height to map within the current instance. + /// + /// Thrown when either , or + /// are negative or not within the bounds that are valid for the current instance. + /// + /// A new instance representing a slice of the current one. + [Pure] + public ReadOnlySpan2D Slice(int row, int column, int width, int height) + { + if ((uint)row >= Height) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + if ((uint)column >= this.width) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + if ((uint)width > (this.width - column)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if ((uint)height > (Height - row)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + int shift = ((this.width + this.pitch) * row) + column; + +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.span), shift); + int pitch = this.pitch + column; + + return new ReadOnlySpan2D(ref r0, height, width, pitch); +#else + unsafe + { + IntPtr offset = (IntPtr)((byte*)this.offset + shift); + + return new ReadOnlySpan2D(this.instance!, offset, height, width, this.pitch); + } +#endif + } + +#if SPAN_RUNTIME_SUPPORT + /// + /// Gets a for a specified row. + /// + /// The index of the target row to retrieve. + /// Throw when is out of range. + /// The resulting row . + [Pure] + public ReadOnlySpan GetRowSpan(int row) + { + if ((uint)row >= (uint)Height) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + int offset = (this.width + this.pitch) * row; + ref T r0 = ref MemoryMarshal.GetReference(this.span); + ref T r1 = ref Unsafe.Add(ref r0, offset); + + return MemoryMarshal.CreateReadOnlySpan(ref r1, this.width); + } +#endif + + /// + /// Tries to get a instance, if the underlying buffer is contiguous. + /// + /// The resulting , in case of success. + /// Whether or not was correctly assigned. + public bool TryGetSpan(out ReadOnlySpan span) + { + if (this.pitch == 0) + { +#if SPAN_RUNTIME_SUPPORT + // We can only create a Span if the buffer is contiguous + span = MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(this.span), Size); + + return true; +#else + // An empty Span2D is still valid + if (this.instance is null) + { + span = default; + + return true; + } + + // Without Span runtime support, we can only get a ReadOnlySpan from a T[] instance + if (this.instance.GetType() == typeof(T[])) + { + span = new ReadOnlySpan(Unsafe.As(this.instance), (int)this.offset, Size); + + return true; + } +#endif + } + + span = default; + + return false; + } + + /// + /// Copies the contents of the current instance into a new 2D array. + /// + /// A 2D array containing the data in the current instance. + [Pure] + public T[,] ToArray() + { + T[,] array = new T[Height, this.width]; + +#if SPAN_RUNTIME_SUPPORT + CopyTo(array.AsSpan()); +#else + // Skip the initialization if the array is empty + if (Size > 0) + { + ref T r0 = ref array.DangerousGetReference(); + IntPtr offset = default; + + // Fallback once again on the enumerator to copy the items + foreach (T item in this) + { + Unsafe.Add(ref r0, offset) = item; + + offset += 1; + } + } +#endif + + return array; + } + +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Equals() on Span will always throw an exception. Use == instead.")] + public override bool Equals(object? obj) + { + throw new NotSupportedException("Microsoft.Toolkit.HighPerformance.ReadOnlySpan2D.Equals(object) is not supported"); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("GetHashCode() on Span will always throw an exception.")] + public override int GetHashCode() + { + throw new NotSupportedException("Microsoft.Toolkit.HighPerformance.ReadOnlySpan2D.GetHashCode() is not supported"); + } +#pragma warning restore CS0809 + + /// + public override string ToString() + { + return $"Microsoft.Toolkit.HighPerformance.Memory.ReadOnlySpan2D<{typeof(T)}>[{Height}, {this.width}]"; + } + + /// + /// Checks whether two instances are equal. + /// + /// The first instance to compare. + /// The second instance to compare. + /// Whether or not and are equal. + public static bool operator ==(ReadOnlySpan2D left, ReadOnlySpan2D right) + { + return +#if SPAN_RUNTIME_SUPPORT + left.span == right.span && +#else + ReferenceEquals(left.instance, right.instance) && + left.offset == right.offset && + left.height == right.height && +#endif + left.width == right.width && + left.pitch == right.pitch; + } + + /// + /// Checks whether two instances are not equal. + /// + /// The first instance to compare. + /// The second instance to compare. + /// Whether or not and are not equal. + public static bool operator !=(ReadOnlySpan2D left, ReadOnlySpan2D right) + { + return !(left == right); + } + + /// + /// Implicily converts a given 2D array into a instance. + /// + /// The input 2D array to convert. + public static implicit operator ReadOnlySpan2D(T[,]? array) => new ReadOnlySpan2D(array); + } +} From cdfee3e3d99b5b6501af5d111deed2b9eb242dbb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 17:24:05 +0200 Subject: [PATCH 044/200] Fixed a bug in Span2D.Empty --- Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 0eefe48c94d..ac426296927 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -338,7 +338,7 @@ public Span2D(T[,,] array, int depth) /// /// Gets an empty instance. /// - public static Span Empty => default; + public static Span2D Empty => default; /// /// Gets a value indicating whether the current instance is empty. From 07cc99eaa4dda54de105910a6d000fee7a4f863c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 17:31:48 +0200 Subject: [PATCH 045/200] Fixed file headers --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 6 +++++- .../Enumerables/RefEnumerable{T}.cs | 6 +++++- .../Memory/Internals/ThrowHelper.cs | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index b9089a438cc..f896f24effd 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -1,4 +1,8 @@ -using System; +// 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.ComponentModel; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 3d02afb8df0..4069fbda5be 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -1,4 +1,8 @@ -using System; +// 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.ComponentModel; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs b/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs index 17e8629832e..123f131ab64 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs @@ -1,4 +1,8 @@ -using System; +// 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; namespace Microsoft.Toolkit.HighPerformance.Memory.Internals { From 71a0954353d918f69d8e839b7d3c6170f473c311 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 19:38:02 +0200 Subject: [PATCH 046/200] Bug fixes in Span2D constructors --- .../Memory/Span2D{T}.cs | 85 ++++++++++++++----- 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index ac426296927..92cc7010a99 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -87,14 +87,22 @@ public readonly ref partial struct Span2D /// The height of the 2D memory area to map. /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map (the distance between each row). - /// - /// Thrown when either , or are negative. - /// + /// Thrown when one of the parameters are negative. public Span2D(ref T value, int height, int width, int pitch) { - if ((height | width | pitch) < 0) + if (width < 0) { - ThrowHelper.ThrowArgumentException(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } this.span = MemoryMarshal.CreateSpan(ref value, height); @@ -109,10 +117,7 @@ public Span2D(ref T value, int height, int width, int pitch) /// The height of the 2D memory area to map. /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map (the distance between each row). - /// - /// Thrown when is a reference type or contains references. Also thrown - /// when either , or are negative. - /// + /// Thrown when one of the parameters are negative. public unsafe Span2D(void* pointer, int height, int width, int pitch) { if (RuntimeHelpers.IsReferenceOrContainsReferences()) @@ -120,9 +125,19 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) ThrowHelper.ThrowArgumentExceptionForManagedType(); } - if ((height | width | pitch) < 0) + if (width < 0) { - ThrowHelper.ThrowArgumentException(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } this.span = new Span(pointer, height); @@ -152,18 +167,18 @@ internal Span2D(object instance, IntPtr offset, int height, int width, int pitch /// Initializes a new instance of the struct. /// /// The target array to wrap. - /// The initial offset within . /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. /// /// Thrown when doesn't match . /// - /// - /// Thrown when either , or are invalid. + /// + /// Thrown when either or are invalid. /// + /// The total area must match the lenght of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span2D(T[] array, int offset, int width, int height) - : this(array, offset, width, height, 0) + public Span2D(T[] array, int width, int height) + : this(array, 0, width, height, 0) { } @@ -179,8 +194,10 @@ public Span2D(T[] array, int offset, int width, int height) /// Thrown when doesn't match . /// /// - /// Thrown when either , , - /// or are invalid. + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span2D(T[] array, int offset, int width, int height, int pitch) @@ -190,14 +207,38 @@ public Span2D(T[] array, int offset, int width, int height, int pitch) ThrowHelper.ThrowArrayTypeMismatchException(); } - if ((uint)offset >= (uint)array.Length) + if ((uint)offset > (uint)array.Length) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); } - int remaining = array.Length - offset; + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + if (width == 0 || height == 0) + { + this = default; + + return; + } + + int + remaining = array.Length - offset, + area = ((width + pitch) * (height - 1)) + width; - if ((((uint)width + (uint)pitch) * (uint)height) > (uint)remaining) + if (area > remaining) { ThrowHelper.ThrowArgumentException(); } @@ -299,7 +340,7 @@ public Span2D(T[,] array, int row, int column, int width, int height) this.height = array.GetLength(0); #endif this.width = width; - this.pitch = row + (array.GetLength(1) - column); + this.pitch = columns - width; } /// From d6df6a2d26f756948f303654510d6863b3bb2008 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 19:41:30 +0200 Subject: [PATCH 047/200] Added unit tests for Span2D constructors --- .../Memory/Test_Span2D{T}.cs | 208 ++++++++++++++++++ ...UnitTests.HighPerformance.Shared.projitems | 1 + 2 files changed, 209 insertions(+) create mode 100644 UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs new file mode 100644 index 00000000000..87472071a9a --- /dev/null +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -0,0 +1,208 @@ +// 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.CodeAnalysis; +using System.Runtime.CompilerServices; +using Microsoft.Toolkit.HighPerformance.Memory; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.HighPerformance.Memory +{ + [TestClass] + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")] + public class Test_Span2DT + { + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_Empty() + { + Span2D empty1 = default; + + Assert.IsTrue(empty1.IsEmpty); + Assert.AreEqual(empty1.Size, 0); + Assert.AreEqual(empty1.Width, 0); + Assert.AreEqual(empty1.Height, 0); + + Span2D empty2 = Span2D.Empty; + + Assert.IsTrue(empty2.IsEmpty); + Assert.AreEqual(empty2.Size, 0); + Assert.AreEqual(empty2.Width, 0); + Assert.AreEqual(empty2.Height, 0); + } + +#if !WINDOWS_UWP + [TestCategory("Span2DT")] + [TestMethod] + public unsafe void Test_Span2DT_RefConstructor() + { + Span span = stackalloc[] + { + 1, 2, 3, 4, 5, 6 + }; + + Span2D span2d = new Span2D(ref span[0], 2, 3, 0); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Width, 3); + Assert.AreEqual(span2d.Height, 2); + + span2d[0, 0] = 99; + span2d[1, 2] = 101; + + Assert.AreEqual(span[0], 99); + Assert.AreEqual(span[5], 101); + + Assert.ThrowsException(() => new Span2D(ref Unsafe.AsRef(null), -1, 0, 0)); + Assert.ThrowsException(() => new Span2D(ref Unsafe.AsRef(null), 1, -2, 0)); + Assert.ThrowsException(() => new Span2D(ref Unsafe.AsRef(null), 1, 0, -5)); + } + + [TestCategory("Span2DT")] + [TestMethod] + public unsafe void Test_Span2DT_PtrConstructor() + { + int* ptr = stackalloc[] + { + 1, 2, 3, 4, 5, 6 + }; + + Span2D span2d = new Span2D(ptr, 2, 3, 0); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Width, 3); + Assert.AreEqual(span2d.Height, 2); + + span2d[0, 0] = 99; + span2d[1, 2] = 101; + + Assert.AreEqual(ptr[0], 99); + Assert.AreEqual(ptr[5], 101); + + Assert.ThrowsException(() => new Span2D((void*)0, -1, 0, 0)); + Assert.ThrowsException(() => new Span2D((void*)0, 1, -2, 0)); + Assert.ThrowsException(() => new Span2D((void*)0, 1, 0, -5)); + } +#endif + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_Array1DConstructor() + { + int[] array = + { + 1, 2, 3, 4, 5, 6 + }; + + Span2D span2d = new Span2D(array, 1, 2, 2, 1); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + + span2d[0, 0] = 99; + span2d[1, 1] = 101; + + Assert.AreEqual(array[1], 99); + Assert.AreEqual(array[5], 101); + + Assert.ThrowsException(() => new Span2D(new string[1], 1, 1)); + Assert.ThrowsException(() => new Span2D(array, -99, 1, 1, 1)); + Assert.ThrowsException(() => new Span2D(array, 0, -10, 1, 1)); + Assert.ThrowsException(() => new Span2D(array, 0, 1, 1, -1)); + Assert.ThrowsException(() => new Span2D(array, 0, 1, -100, 1)); + Assert.ThrowsException(() => new Span2D(array, 0, 10, 1, 120)); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_Array2DConstructor_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Width, 3); + Assert.AreEqual(span2d.Height, 2); + + span2d[0, 1] = 99; + span2d[1, 2] = 101; + + Assert.AreEqual(array[0, 1], 99); + Assert.AreEqual(array[1, 2], 101); + + Assert.ThrowsException(() => new Span2D(new string[1, 2])); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_Array2DConstructor_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array, 0, 1, 2, 2); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + + span2d[0, 0] = 99; + span2d[1, 1] = 101; + + Assert.AreEqual(array[0, 1], 99); + Assert.AreEqual(array[1, 2], 101); + + Assert.ThrowsException(() => new Span2D(new string[1, 2], 0, 0, 2, 2)); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_Array3DConstructo() + { + int[,,] array = + { + { + { 1, 2, 3 }, + { 4, 5, 6 } + }, + { + { 10, 20, 30 }, + { 40, 50, 60 } + } + }; + + Span2D span2d = new Span2D(array, 1); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Width, 3); + Assert.AreEqual(span2d.Height, 2); + + span2d[0, 1] = 99; + span2d[1, 2] = 101; + + Assert.AreEqual(span2d[0, 0], 10); + Assert.AreEqual(array[1, 0, 1], 99); + Assert.AreEqual(array[1, 1, 2], 101); + + Assert.ThrowsException(() => new Span2D(array, -1)); + Assert.ThrowsException(() => new Span2D(array, 20)); + } + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems index cb1c3a5a85f..61eb927075b 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems +++ b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems @@ -39,6 +39,7 @@ + From 5f0162d71e799ae8edb9c6b69f1f72df092698fd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 19:48:16 +0200 Subject: [PATCH 048/200] Added new Span2D T[,,] constructor and test --- .../Memory/Span2D{T}.cs | 67 ++++++++++++++++++- .../Memory/Test_Span2D{T}.cs | 39 ++++++++++- 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 92cc7010a99..6b45ff87bbb 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -322,12 +322,12 @@ public Span2D(T[,] array, int row, int column, int width, int height) ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } - if (width > (columns - column)) + if ((uint)width > (uint)(columns - column)) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } - if (height > (rows - row)) + if ((uint)height > (uint)(rows - row)) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } @@ -351,7 +351,7 @@ public Span2D(T[,] array, int row, int column, int width, int height) /// /// Thrown when doesn't match . /// - /// Thrown when either is invalid. + /// Thrown when a parameter is invalid. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span2D(T[,,] array, int depth) { @@ -376,6 +376,67 @@ public Span2D(T[,,] array, int depth) this.pitch = 0; } + /// + /// Initializes a new instance of the struct wrapping a layer in a 3D array. + /// + /// The given 3D array to wrap. + /// The target layer to map within . + /// The target row to map within . + /// The target column to map within . + /// The width to map within . + /// The height to map within . + /// + /// Thrown when doesn't match . + /// + /// Thrown when a parameter is invalid. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span2D(T[,,] array, int depth, int row, int column, int width, int height) + { + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } + + if ((uint)depth >= (uint)array.GetLength(0)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); + } + + int + rows = array.GetLength(1), + columns = array.GetLength(2); + + if ((uint)row >= (uint)rows) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + if ((uint)column >= (uint)columns) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + if ((uint)width > (uint)(columns - column)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if ((uint)height > (uint)(rows - row)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + +#if SPAN_RUNTIME_SUPPORT + this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(depth, row, column), height); +#else + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, row, column)); + this.height = height; +#endif + this.width = width; + this.pitch = columns - width; + } + /// /// Gets an empty instance. /// diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs index 87472071a9a..711d5e201f0 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -173,7 +173,7 @@ public void Test_Span2DT_Array2DConstructor_2() [TestCategory("Span2DT")] [TestMethod] - public void Test_Span2DT_Array3DConstructo() + public void Test_Span2DT_Array3DConstructor_1() { int[,,] array = { @@ -204,5 +204,42 @@ public void Test_Span2DT_Array3DConstructo() Assert.ThrowsException(() => new Span2D(array, -1)); Assert.ThrowsException(() => new Span2D(array, 20)); } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_Array3DConstructor_2() + { + int[,,] array = + { + { + { 1, 2, 3 }, + { 4, 5, 6 } + }, + { + { 10, 20, 30 }, + { 40, 50, 60 } + } + }; + + Span2D span2d = new Span2D(array, 1, 0, 1, 2, 2); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + + span2d[0, 1] = 99; + span2d[1, 1] = 101; + + Assert.AreEqual(span2d[0, 0], 20); + Assert.AreEqual(array[1, 0, 2], 99); + Assert.AreEqual(array[1, 1, 2], 101); + + Assert.ThrowsException(() => new Span2D(array, -1, 1, 1, 1, 1)); + Assert.ThrowsException(() => new Span2D(array, 1, -1, 1, 1, 1)); + Assert.ThrowsException(() => new Span2D(array, 1, 1, -1, 1, 1)); + Assert.ThrowsException(() => new Span2D(array, 1, 1, 1, -1, 1)); + Assert.ThrowsException(() => new Span2D(array, 1, 1, 1, 1, -1)); + } } } \ No newline at end of file From 0d610017151c1e26a236af7160f8bd0934325dd2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 20:16:06 +0200 Subject: [PATCH 049/200] Added more Span2D tests --- .../Memory/Test_Span2D{T}.cs | 424 ++++++++++++++++++ 1 file changed, 424 insertions(+) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs index 711d5e201f0..e3d556935b7 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Memory; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -241,5 +242,428 @@ public void Test_Span2DT_Array3DConstructor_2() Assert.ThrowsException(() => new Span2D(array, 1, 1, 1, -1, 1)); Assert.ThrowsException(() => new Span2D(array, 1, 1, 1, 1, -1)); } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_FillAndClear_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + span2d.Fill(42); + + Assert.IsTrue(array.Cast().All(n => n == 42)); + + span2d.Clear(); + + Assert.IsTrue(array.Cast().All(n => n == 0)); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_FillAndClear_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array, 0, 1, 2, 2); + + span2d.Fill(42); + + int[,] filled = + { + { 1, 42, 42 }, + { 4, 42, 42 } + }; + + CollectionAssert.AreEqual(array, filled); + + span2d.Clear(); + + int[,] cleared = + { + { 1, 0, 0 }, + { 4, 0, 0 } + }; + + CollectionAssert.AreEqual(array, cleared); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_CopyTo_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + int[] target = new int[array.Length]; + + span2d.CopyTo(target); + + CollectionAssert.AreEqual(array, target); + + Assert.ThrowsException(() => new Span2D(array).CopyTo(Span.Empty)); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_CopyTo_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array, 0, 1, 2, 2); + + int[] target = new int[4]; + + span2d.CopyTo(target); + + int[] expected = { 2, 3, 5, 6 }; + + CollectionAssert.AreEqual(target, expected); + + Assert.ThrowsException(() => new Span2D(array).CopyTo(Span.Empty)); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_CopyTo2D_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + int[,] target = new int[2, 3]; + + span2d.CopyTo(target); + + CollectionAssert.AreEqual(array, target); + + Assert.ThrowsException(() => new Span2D(array).CopyTo(Span2D.Empty)); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_CopyTo2D_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array, 0, 1, 2, 2); + + int[,] target = new int[2, 2]; + + span2d.CopyTo(target); + + int[,] expected = + { + { 2, 3 }, + { 5, 6 } + }; + + CollectionAssert.AreEqual(target, expected); + + Assert.ThrowsException(() => new Span2D(array).CopyTo(new Span2D(target))); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_TryCopyTo() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + int[] target = new int[array.Length]; + + Assert.IsTrue(span2d.TryCopyTo(target)); + Assert.IsFalse(span2d.TryCopyTo(Span.Empty)); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_TryCopyTo2D() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + int[,] target = new int[2, 3]; + + Assert.IsTrue(span2d.TryCopyTo(target)); + Assert.IsFalse(span2d.TryCopyTo(Span2D.Empty)); + } + + [TestCategory("Span2DT")] + [TestMethod] + public unsafe void Test_Span2DT_GetPinnableReference() + { + Assert.IsTrue(Unsafe.AreSame( + ref Unsafe.AsRef(null), + ref Span2D.Empty.GetPinnableReference())); + + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + ref int r0 = ref span2d.GetPinnableReference(); + + Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0])); + } + + [TestCategory("Span2DT")] + [TestMethod] + public unsafe void Test_Span2DT_DangerousGetReference() + { + Assert.IsTrue(Unsafe.AreSame( + ref Unsafe.AsRef(null), + ref Span2D.Empty.DangerousGetReference())); + + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + ref int r0 = ref span2d.DangerousGetReference(); + + Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0])); + } + +#if !WINDOWS_UWP + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_GetRowSpan() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + Span span = span2d.GetRowSpan(1); + + Assert.IsTrue(Unsafe.AreSame( + ref span[0], + ref array[1, 0])); + Assert.IsTrue(Unsafe.AreSame( + ref span[2], + ref array[1, 2])); + + Assert.ThrowsException(() => new Span2D(array).GetRowSpan(-1)); + Assert.ThrowsException(() => new Span2D(array).GetRowSpan(5)); + } +#endif + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_TryGetSpan_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + bool success = span2d.TryGetSpan(out Span span); + + Assert.IsTrue(success); + Assert.AreEqual(span.Length, span2d.Size); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_TryGetSpan_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array, 0, 0, 2, 2); + + bool success = span2d.TryGetSpan(out Span span); + + Assert.IsFalse(success); + Assert.IsTrue(span.IsEmpty); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_ToArray_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + int[,] copy = span2d.ToArray(); + + Assert.AreEqual(copy.GetLength(0), array.GetLength(0)); + Assert.AreEqual(copy.GetLength(1), array.GetLength(1)); + + CollectionAssert.AreEqual(array, copy); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_ToArray_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array, 0, 0, 2, 2); + + int[,] copy = span2d.ToArray(); + + Assert.AreEqual(copy.GetLength(0), 2); + Assert.AreEqual(copy.GetLength(1), 2); + + int[,] expected = + { + { 1, 2 }, + { 4, 5 } + }; + + CollectionAssert.AreEqual(expected, copy); + } + + [TestCategory("Span2DT")] + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Test_Span2DT_Equals() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + _ = span2d.Equals(null); + } + + [TestCategory("Span2DT")] + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Test_Span2DT_GetHashCode() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + _ = span2d.GetHashCode(); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_ToString() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + string text = span2d.ToString(); + + const string expected = "Microsoft.Toolkit.HighPerformance.Memory.Span2D[2, 3]"; + + Assert.AreEqual(text, expected); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_ToString_opEquals() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d_1 = new Span2D(array); + Span2D span2d_2 = new Span2D(array); + + Assert.IsTrue(span2d_1 == span2d_2); + Assert.IsFalse(span2d_1 == Span2D.Empty); + Assert.IsTrue(Span2D.Empty == Span2D.Empty); + + Span2D span2d_3 = new Span2D(array, 0, 0, 2, 2); + + Assert.IsFalse(span2d_1 == span2d_3); + Assert.IsFalse(span2d_3 == Span2D.Empty); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_ToString_ImplicitCast() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d_1 = array; + Span2D span2d_2 = new Span2D(array); + + Assert.IsTrue(span2d_1 == span2d_2); + } } } \ No newline at end of file From 5f3368f02f76c950b01f5ca70e559493a06d4ca0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 20:34:25 +0200 Subject: [PATCH 050/200] Added Span2D.Slice tests --- .../Memory/Span2D{T}.cs | 7 +- .../Memory/Test_Span2D{T}.cs | 74 +++++++++++++++++++ 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 6b45ff87bbb..8647cef535b 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -760,11 +760,12 @@ public Span2D Slice(int row, int column, int width, int height) ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - int shift = ((this.width + this.pitch) * row) + column; + int + shift = ((this.width + this.pitch) * row) + column, + pitch = this.pitch + (this.width - width); #if SPAN_RUNTIME_SUPPORT ref T r0 = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.span), shift); - int pitch = this.pitch + column; return new Span2D(ref r0, height, width, pitch); #else @@ -772,7 +773,7 @@ public Span2D Slice(int row, int column, int width, int height) { IntPtr offset = (IntPtr)((byte*)this.offset + shift); - return new Span2D(this.instance!, offset, height, width, this.pitch); + return new Span2D(this.instance!, offset, height, width, pitch); } #endif } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs index e3d556935b7..ff16d5e4784 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -467,6 +467,80 @@ ref Unsafe.AsRef(null), Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0])); } + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_Slice_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + Span2D slice1 = span2d.Slice(1, 1, 2, 1); + + Assert.AreEqual(slice1.Size, 2); + Assert.AreEqual(slice1.Height, 1); + Assert.AreEqual(slice1.Width, 2); + Assert.AreEqual(slice1[0, 0], 5); + Assert.AreEqual(slice1[0, 1], 6); + + Span2D slice2 = span2d.Slice(0, 1, 2, 2); + + Assert.AreEqual(slice2.Size, 4); + Assert.AreEqual(slice2.Height, 2); + Assert.AreEqual(slice2.Width, 2); + Assert.AreEqual(slice2[0, 0], 2); + Assert.AreEqual(slice2[1, 0], 5); + Assert.AreEqual(slice2[1, 1], 6); + + Assert.ThrowsException(() => new Span2D(array).Slice(-1, 1, 1, 1)); + Assert.ThrowsException(() => new Span2D(array).Slice(1, -1, 1, 1)); + Assert.ThrowsException(() => new Span2D(array).Slice(1, 1, -1, 1)); + Assert.ThrowsException(() => new Span2D(array).Slice(1, 1, 1, -1)); + Assert.ThrowsException(() => new Span2D(array).Slice(10, 1, 1, 1)); + Assert.ThrowsException(() => new Span2D(array).Slice(1, 12, 12, 1)); + Assert.ThrowsException(() => new Span2D(array).Slice(1, 1, 1, 55)); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_Slice_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array); + + Span2D slice1 = span2d.Slice(0, 0, 2, 2); + + Assert.AreEqual(slice1.Size, 4); + Assert.AreEqual(slice1.Height, 2); + Assert.AreEqual(slice1.Width, 2); + Assert.AreEqual(slice1[0, 0], 1); + Assert.AreEqual(slice1[1, 1], 5); + + Span2D slice2 = slice1.Slice(1, 0, 2, 1); + + Assert.AreEqual(slice2.Size, 2); + Assert.AreEqual(slice2.Height, 1); + Assert.AreEqual(slice2.Width, 2); + Assert.AreEqual(slice2[0, 0], 4); + Assert.AreEqual(slice2[0, 1], 5); + + Span2D slice3 = slice2.Slice(0, 1, 1, 1); + + Assert.AreEqual(slice3.Size, 1); + Assert.AreEqual(slice3.Height, 1); + Assert.AreEqual(slice3.Width, 1); + Assert.AreEqual(slice3[0, 0], 5); + } + #if !WINDOWS_UWP [TestCategory("Span2DT")] [TestMethod] From 744d8e94e90c1808ffdee6f2a2c1ebe4c99aeac5 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 22:08:46 +0200 Subject: [PATCH 051/200] Fixed RefEnumerable.ToArray --- .../Enumerables/RefEnumerable{T}.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 4069fbda5be..17c766f8b7c 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -161,12 +161,18 @@ public T[] ToArray() return Array.Empty(); } +#if SPAN_RUNTIME_SUPPORT + ref T sourceRef = ref this.span.DangerousGetReference(); +#else + ref T sourceRef = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); +#endif + T[] array = new T[length / this.step]; - ref T r0 = ref array.DangerousGetReference(); + ref T destinationRef = ref array.DangerousGetReference(); for (int i = 0, j = 0; i < length; i += this.step, j++) { - Unsafe.Add(ref r0, j) = Unsafe.Add(ref r0, i); + Unsafe.Add(ref destinationRef, j) = Unsafe.Add(ref sourceRef, i); } return array; From 64d51c0bc043afb55037137a54bb402690c8486f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 22:08:53 +0200 Subject: [PATCH 052/200] Finished Span2D tests --- .../Memory/Test_Span2D{T}.cs | 79 ++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs index ff16d5e4784..c0caa4a54cc 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; +using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Memory; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -703,7 +704,7 @@ public void Test_Span2DT_ToString() [TestCategory("Span2DT")] [TestMethod] - public void Test_Span2DT_ToString_opEquals() + public void Test_Span2DT_opEquals() { int[,] array = { @@ -726,7 +727,7 @@ public void Test_Span2DT_ToString_opEquals() [TestCategory("Span2DT")] [TestMethod] - public void Test_Span2DT_ToString_ImplicitCast() + public void Test_Span2DT_ImplicitCast() { int[,] array = { @@ -739,5 +740,79 @@ public void Test_Span2DT_ToString_ImplicitCast() Assert.IsTrue(span2d_1 == span2d_2); } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_GetRow() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + RefEnumerable enumerable = new Span2D(array).GetRow(1); + + int[] expected = { 4, 5, 6 }; + + CollectionAssert.AreEqual(enumerable.ToArray(), expected); + + Assert.ThrowsException(() => new Span2D(array).GetRow(-1)); + Assert.ThrowsException(() => new Span2D(array).GetRow(2)); + Assert.ThrowsException(() => new Span2D(array).GetRow(1000)); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_GetColumn() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + RefEnumerable enumerable = new Span2D(array).GetColumn(2); + + int[] expected = { 3, 6 }; + + CollectionAssert.AreEqual(enumerable.ToArray(), expected); + + Assert.ThrowsException(() => new Span2D(array).GetColumn(-1)); + Assert.ThrowsException(() => new Span2D(array).GetColumn(3)); + Assert.ThrowsException(() => new Span2D(array).GetColumn(1000)); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_GetEnumerator() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + int[] result = new int[4]; + int i = 0; + + foreach (var item in new Span2D(array, 0, 1, 2, 2)) + { + result[i++] = item; + } + + int[] expected = { 2, 3, 5, 6 }; + + CollectionAssert.AreEqual(result, expected); + } + + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_GetEnumerator_Empty() + { + var enumerator = Span2D.Empty.GetEnumerator(); + + Assert.IsFalse(enumerator.MoveNext()); + } } } \ No newline at end of file From 4cf78e4c0df73ddbf5403039dbf63ecbaae472aa Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 22:15:23 +0200 Subject: [PATCH 053/200] Fixed bugs in ReadOnlyRefEnumerable --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index f896f24effd..dbcd0ce784e 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -61,9 +61,9 @@ public ref struct ReadOnlyRefEnumerable /// The number of items in the sequence. /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlyRefEnumerable(ref T reference, int length, int step) + internal ReadOnlyRefEnumerable(in T reference, int length, int step) { - this.span = MemoryMarshal.CreateReadOnlySpan(ref reference, length * step); + this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length * step); this.step = step; this.position = 0; } @@ -130,16 +130,7 @@ public ref readonly T Current } } - /// - /// Returns a array with the values in the target row. - /// - /// A array with the values in the target row. - /// - /// This method will allocate a new array, so only - /// use it if you really need to copy the target items in a new memory location. - /// Additionally, this method will always return the whole sequence from the start, - /// ignoring the current position in case the sequence has already been enumerated in part. - /// + /// [Pure] public T[] ToArray() { @@ -161,12 +152,18 @@ public T[] ToArray() return Array.Empty(); } +#if SPAN_RUNTIME_SUPPORT + ref T sourceRef = ref this.span.DangerousGetReference(); +#else + ref T sourceRef = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); +#endif + T[] array = new T[length / this.step]; - ref T r0 = ref array.DangerousGetReference(); + ref T destinationRef = ref array.DangerousGetReference(); for (int i = 0, j = 0; i < length; i += this.step, j++) { - Unsafe.Add(ref r0, j) = Unsafe.Add(ref r0, i); + Unsafe.Add(ref destinationRef, j) = Unsafe.Add(ref sourceRef, i); } return array; From 088bda66c3349bb1aa92393a240d46f4097b57b8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 22:32:07 +0200 Subject: [PATCH 054/200] Updated ReadOnlySpan2D with changes from Span2D --- .../Memory/ReadOnlySpan2D{T}.Enumerator.cs | 8 +- .../Memory/ReadOnlySpan2D{T}.cs | 202 ++++++++++++++---- .../Memory/Span2D{T}.Enumerator.cs | 18 +- .../Memory/Span2D{T}.cs | 80 +++---- 4 files changed, 212 insertions(+), 96 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs index bd66f7fff7c..c766cd7cc1d 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs @@ -37,7 +37,7 @@ public ReadOnlyRefEnumerable GetRow(int row) ref T r1 = ref Unsafe.Add(ref r0, (this.width + this.pitch) * row); #if SPAN_RUNTIME_SUPPORT - return new ReadOnlyRefEnumerable(ref r1, Width, 1); + return new RefEnumerable(ref r1, Width, 1); #else IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); @@ -64,7 +64,7 @@ public ReadOnlyRefEnumerable GetColumn(int column) ref T r1 = ref Unsafe.Add(ref r0, column); #if SPAN_RUNTIME_SUPPORT - return new ReadOnlyRefEnumerable(ref r1, Height, this.width + this.pitch); + return new ReadOnlyRefEnumerable(ref r1, Height, this.width + this.Pitch); #else IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); @@ -89,10 +89,10 @@ public ref struct Enumerator { #if SPAN_RUNTIME_SUPPORT /// - /// The instance pointing to the first item in the target memory area. + /// The instance pointing to the first item in the target memory area. /// /// Just like in , the length is the height of the 2D region. - private readonly ReadOnlySpan span; + private readonly Span span; #else /// /// The target instance, if present. diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 588745fad31..6478e01dbb8 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -58,17 +58,25 @@ public readonly ref partial struct ReadOnlySpan2D /// The height of the 2D memory area to map. /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map (the distance between each row). - /// - /// Thrown when either , or are negative. - /// - public ReadOnlySpan2D(ref T value, int height, int width, int pitch) + /// Thrown when one of the parameters are negative. + public ReadOnlySpan2D(in T value, int height, int width, int pitch) { - if ((height | width | pitch) < 0) + if (width < 0) { - ThrowHelper.ThrowArgumentException(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } - this.span = MemoryMarshal.CreateReadOnlySpan(ref value, height); + this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(value), height); this.width = width; this.pitch = pitch; } @@ -80,10 +88,7 @@ public ReadOnlySpan2D(ref T value, int height, int width, int pitch) /// The height of the 2D memory area to map. /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map (the distance between each row). - /// - /// Thrown when is a reference type or contains references. Also thrown - /// when either , or are negative. - /// + /// Thrown when one of the parameters are negative. public unsafe ReadOnlySpan2D(void* pointer, int height, int width, int pitch) { if (RuntimeHelpers.IsReferenceOrContainsReferences()) @@ -91,9 +96,19 @@ public unsafe ReadOnlySpan2D(void* pointer, int height, int width, int pitch) ThrowHelper.ThrowArgumentExceptionForManagedType(); } - if ((height | width | pitch) < 0) + if (width < 0) { - ThrowHelper.ThrowArgumentException(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } this.span = new Span(pointer, height); @@ -123,18 +138,18 @@ internal ReadOnlySpan2D(object instance, IntPtr offset, int height, int width, i /// Initializes a new instance of the struct. /// /// The target array to wrap. - /// The initial offset within . /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. /// /// Thrown when doesn't match . /// - /// - /// Thrown when either , or are invalid. + /// + /// Thrown when either or are invalid. /// + /// The total area must match the lenght of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan2D(T[] array, int offset, int width, int height) - : this(array, offset, width, height, 0) + public ReadOnlySpan2D(T[] array, int width, int height) + : this(array, 0, width, height, 0) { } @@ -150,8 +165,10 @@ public ReadOnlySpan2D(T[] array, int offset, int width, int height) /// Thrown when doesn't match . /// /// - /// Thrown when either , , - /// or are invalid. + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan2D(T[] array, int offset, int width, int height, int pitch) @@ -161,14 +178,38 @@ public ReadOnlySpan2D(T[] array, int offset, int width, int height, int pitch) ThrowHelper.ThrowArrayTypeMismatchException(); } - if ((uint)offset >= (uint)array.Length) + if ((uint)offset > (uint)array.Length) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); } - int remaining = array.Length - offset; + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } - if ((((uint)width + (uint)pitch) * (uint)height) > (uint)remaining) + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + if (width == 0 || height == 0) + { + this = default; + + return; + } + + int + remaining = array.Length - offset, + area = ((width + pitch) * (height - 1)) + width; + + if (area > remaining) { ThrowHelper.ThrowArgumentException(); } @@ -252,12 +293,12 @@ public ReadOnlySpan2D(T[,] array, int row, int column, int width, int height) ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } - if (width > (columns - column)) + if ((uint)width > (uint)(columns - column)) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } - if (height > (rows - row)) + if ((uint)height > (uint)(rows - row)) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } @@ -270,7 +311,7 @@ public ReadOnlySpan2D(T[,] array, int row, int column, int width, int height) this.height = array.GetLength(0); #endif this.width = width; - this.pitch = row + (array.GetLength(1) - column); + this.pitch = columns - width; } /// @@ -281,7 +322,7 @@ public ReadOnlySpan2D(T[,] array, int row, int column, int width, int height) /// /// Thrown when doesn't match . /// - /// Thrown when either is invalid. + /// Thrown when a parameter is invalid. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan2D(T[,,] array, int depth) { @@ -306,6 +347,67 @@ public ReadOnlySpan2D(T[,,] array, int depth) this.pitch = 0; } + /// + /// Initializes a new instance of the struct wrapping a layer in a 3D array. + /// + /// The given 3D array to wrap. + /// The target layer to map within . + /// The target row to map within . + /// The target column to map within . + /// The width to map within . + /// The height to map within . + /// + /// Thrown when doesn't match . + /// + /// Thrown when a parameter is invalid. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan2D(T[,,] array, int depth, int row, int column, int width, int height) + { + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } + + if ((uint)depth >= (uint)array.GetLength(0)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); + } + + int + rows = array.GetLength(1), + columns = array.GetLength(2); + + if ((uint)row >= (uint)rows) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + if ((uint)column >= (uint)columns) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + if ((uint)width > (uint)(columns - column)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if ((uint)height > (uint)(rows - row)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + +#if SPAN_RUNTIME_SUPPORT + this.span = MemoryMarshal.CreateReadOnlySpan(ref array.DangerousGetReferenceAt(depth, row, column), height); +#else + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, row, column)); + this.height = height; +#endif + this.width = width; + this.pitch = columns - width; + } + /// /// Gets an empty instance. /// @@ -386,11 +488,11 @@ public int Width } /// - /// Copies the contents of this into a destination instance. + /// Copies the contents of this into a destination instance. /// /// The destination instance. /// - /// Thrown when is shorter than the source instance. + /// Thrown when is shorter than the source instance. /// public void CopyTo(Span destination) { @@ -455,7 +557,7 @@ public void CopyTo(Span2D destination) GetRowSpan(i).CopyTo(destination.GetRowSpan(i)); } #else - Span2D.Enumerator destinationEnumerator = destination.GetEnumerator(); + Enumerator destinationEnumerator = destination.GetEnumerator(); // Fallback path with two enumerators foreach (T item in this) @@ -469,7 +571,7 @@ public void CopyTo(Span2D destination) } /// - /// Attempts to copy the current instance to a destination . + /// Attempts to copy the current instance to a destination . /// /// The target of the copy operation. /// Whether or not the operation was successful. @@ -486,7 +588,7 @@ public bool TryCopyTo(Span destination) } /// - /// Attempts to copy the current instance to a destination . + /// Attempts to copy the current instance to a destination . /// /// The target of the copy operation. /// Whether or not the operation was successful. @@ -550,7 +652,7 @@ public ref T DangerousGetReference() /// Thrown when either , or /// are negative or not within the bounds that are valid for the current instance. /// - /// A new instance representing a slice of the current one. + /// A new instance representing a slice of the current one. [Pure] public ReadOnlySpan2D Slice(int row, int column, int width, int height) { @@ -574,19 +676,20 @@ public ReadOnlySpan2D Slice(int row, int column, int width, int height) ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - int shift = ((this.width + this.pitch) * row) + column; + int + shift = ((this.width + this.pitch) * row) + column, + pitch = this.pitch + (this.width - width); #if SPAN_RUNTIME_SUPPORT ref T r0 = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.span), shift); - int pitch = this.pitch + column; - return new ReadOnlySpan2D(ref r0, height, width, pitch); + return new ReadOnlySpan2D(r0, height, width, pitch); #else unsafe { IntPtr offset = (IntPtr)((byte*)this.offset + shift); - return new ReadOnlySpan2D(this.instance!, offset, height, width, this.pitch); + return new ReadOnlySpan2D(this.instance!, offset, height, width, pitch); } #endif } @@ -637,10 +740,10 @@ public bool TryGetSpan(out ReadOnlySpan span) return true; } - // Without Span runtime support, we can only get a ReadOnlySpan from a T[] instance + // Without Span runtime support, we can only get a Span from a T[] instance if (this.instance.GetType() == typeof(T[])) { - span = new ReadOnlySpan(Unsafe.As(this.instance), (int)this.offset, Size); + span = Unsafe.As(this.instance).AsSpan((int)this.offset, Size); return true; } @@ -653,9 +756,9 @@ public bool TryGetSpan(out ReadOnlySpan span) } /// - /// Copies the contents of the current instance into a new 2D array. + /// Copies the contents of the current instance into a new 2D array. /// - /// A 2D array containing the data in the current instance. + /// A 2D array containing the data in the current instance. [Pure] public T[,] ToArray() { @@ -684,7 +787,7 @@ public bool TryGetSpan(out ReadOnlySpan span) } #pragma warning disable CS0809 // Obsolete member overrides non-obsolete member - /// + /// [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Equals() on Span will always throw an exception. Use == instead.")] public override bool Equals(object? obj) @@ -692,7 +795,7 @@ public override bool Equals(object? obj) throw new NotSupportedException("Microsoft.Toolkit.HighPerformance.ReadOnlySpan2D.Equals(object) is not supported"); } - /// + /// [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("GetHashCode() on Span will always throw an exception.")] public override int GetHashCode() @@ -743,5 +846,18 @@ public override string ToString() /// /// The input 2D array to convert. public static implicit operator ReadOnlySpan2D(T[,]? array) => new ReadOnlySpan2D(array); + + /// + /// Implicily converts a given into a instance. + /// + /// The input to convert. + public static implicit operator ReadOnlySpan2D(Span2D span) + { +#if SPAN_RUNTIME_SUPPORT + return new ReadOnlySpan2D(span.DangerousGetReference(), span.Height, span.Width, span.Pitch); +#else + return new ReadOnlySpan2D(span.Instance!, span.Offset, span.Height, span.Width, span.Pitch); +#endif + } } } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index a028f7d49c0..c296b9ecf1d 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -34,14 +34,14 @@ public RefEnumerable GetRow(int row) } ref T r0 = ref this.DangerousGetReference(); - ref T r1 = ref Unsafe.Add(ref r0, (this.width + this.pitch) * row); + ref T r1 = ref Unsafe.Add(ref r0, (this.width + this.Pitch) * row); #if SPAN_RUNTIME_SUPPORT return new RefEnumerable(ref r1, Width, 1); #else - IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); + IntPtr offset = this.Instance!.DangerousGetObjectDataByteOffset(ref r1); - return new RefEnumerable(this.instance!, offset, this.width, 1); + return new RefEnumerable(this.Instance!, offset, this.width, 1); #endif } @@ -64,11 +64,11 @@ public RefEnumerable GetColumn(int column) ref T r1 = ref Unsafe.Add(ref r0, column); #if SPAN_RUNTIME_SUPPORT - return new RefEnumerable(ref r1, Height, this.width + this.pitch); + return new RefEnumerable(ref r1, Height, this.width + this.Pitch); #else - IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); + IntPtr offset = this.Instance!.DangerousGetObjectDataByteOffset(ref r1); - return new RefEnumerable(this.instance!, offset, Height, this.width + this.pitch); + return new RefEnumerable(this.Instance!, offset, Height, this.width + this.Pitch); #endif } @@ -139,12 +139,12 @@ internal Enumerator(Span2D span) #if SPAN_RUNTIME_SUPPORT this.span = span.span; #else - this.instance = span.instance; - this.offset = span.offset; + this.instance = span.Instance; + this.offset = span.Offset; this.height = span.height; #endif this.width = span.width; - this.pitch = span.pitch; + this.pitch = span.Pitch; this.x = -1; this.y = 0; } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 8647cef535b..ac63a0e2935 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -56,12 +56,12 @@ public readonly ref partial struct Span2D /// /// The target instance, if present. /// - private readonly object? instance; + internal readonly object? Instance; /// - /// The initial offset within . + /// The initial offset within . /// - private readonly IntPtr offset; + internal readonly IntPtr Offset; /// /// The height of the specified 2D region. @@ -77,7 +77,7 @@ public readonly ref partial struct Span2D /// /// The pitch of the specified 2D region. /// - private readonly int pitch; + internal readonly int Pitch; #if SPAN_RUNTIME_SUPPORT /// @@ -107,7 +107,7 @@ public Span2D(ref T value, int height, int width, int pitch) this.span = MemoryMarshal.CreateSpan(ref value, height); this.width = width; - this.pitch = pitch; + this.Pitch = pitch; } /// @@ -142,24 +142,24 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) this.span = new Span(pointer, height); this.width = width; - this.pitch = pitch; + this.Pitch = pitch; } #else /// /// Initializes a new instance of the struct with the specified parameters. /// /// The target instance. - /// The initial offset within . + /// The initial offset within . /// The height of the 2D memory area to map. /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map. internal Span2D(object instance, IntPtr offset, int height, int width, int pitch) { - this.instance = instance; - this.offset = offset; + this.Instance = instance; + this.Offset = offset; this.height = height; this.width = width; - this.pitch = pitch; + this.Pitch = pitch; } #endif @@ -246,12 +246,12 @@ public Span2D(T[] array, int offset, int width, int height, int pitch) #if SPAN_RUNTIME_SUPPORT this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(offset), height); #else - this.instance = array; - this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)); + this.Instance = array; + this.Offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)); this.height = height; #endif this.width = width; - this.pitch = pitch; + this.Pitch = pitch; } /// @@ -278,12 +278,12 @@ public Span2D(T[,]? array) #if SPAN_RUNTIME_SUPPORT this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReference(), array.GetLength(0)); #else - this.instance = array; - this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(0, 0)); + this.Instance = array; + this.Offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(0, 0)); this.height = array.GetLength(0); #endif this.width = array.GetLength(1); - this.pitch = 0; + this.Pitch = 0; } /// @@ -335,12 +335,12 @@ public Span2D(T[,] array, int row, int column, int width, int height) #if SPAN_RUNTIME_SUPPORT this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(row, column), height); #else - this.instance = array; - this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column)); + this.Instance = array; + this.Offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column)); this.height = array.GetLength(0); #endif this.width = width; - this.pitch = columns - width; + this.Pitch = columns - width; } /// @@ -368,12 +368,12 @@ public Span2D(T[,,] array, int depth) #if SPAN_RUNTIME_SUPPORT this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(depth, 0, 0), array.GetLength(1)); #else - this.instance = array; - this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0)); + this.Instance = array; + this.Offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0)); this.height = array.GetLength(1); #endif this.width = array.GetLength(2); - this.pitch = 0; + this.Pitch = 0; } /// @@ -429,12 +429,12 @@ public Span2D(T[,,] array, int depth, int row, int column, int width, int height #if SPAN_RUNTIME_SUPPORT this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(depth, row, column), height); #else - this.instance = array; - this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, row, column)); + this.Instance = array; + this.Offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, row, column)); this.height = height; #endif this.width = width; - this.pitch = columns - width; + this.Pitch = columns - width; } /// @@ -508,9 +508,9 @@ public int Width #if SPAN_RUNTIME_SUPPORT ref T r0 = ref MemoryMarshal.GetReference(this.span); #else - ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + ref T r0 = ref this.Instance!.DangerousGetObjectDataReferenceAt(this.Offset); #endif - int index = (i * (this.width + this.pitch)) + j; + int index = (i * (this.width + this.Pitch)) + j; return ref Unsafe.Add(ref r0, index); } @@ -721,7 +721,7 @@ public ref T DangerousGetReference() #if SPAN_RUNTIME_SUPPORT return ref MemoryMarshal.GetReference(this.span); #else - return ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + return ref this.Instance!.DangerousGetObjectDataReferenceAt(this.Offset); #endif } @@ -761,8 +761,8 @@ public Span2D Slice(int row, int column, int width, int height) } int - shift = ((this.width + this.pitch) * row) + column, - pitch = this.pitch + (this.width - width); + shift = ((this.width + this.Pitch) * row) + column, + pitch = this.Pitch + (this.width - width); #if SPAN_RUNTIME_SUPPORT ref T r0 = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.span), shift); @@ -771,9 +771,9 @@ public Span2D Slice(int row, int column, int width, int height) #else unsafe { - IntPtr offset = (IntPtr)((byte*)this.offset + shift); + IntPtr offset = (IntPtr)((byte*)this.Offset + shift); - return new Span2D(this.instance!, offset, height, width, pitch); + return new Span2D(this.Instance!, offset, height, width, pitch); } #endif } @@ -793,7 +793,7 @@ public Span GetRowSpan(int row) ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); } - int offset = (this.width + this.pitch) * row; + int offset = (this.width + this.Pitch) * row; ref T r0 = ref MemoryMarshal.GetReference(this.span); ref T r1 = ref Unsafe.Add(ref r0, offset); @@ -808,7 +808,7 @@ public Span GetRowSpan(int row) /// Whether or not was correctly assigned. public bool TryGetSpan(out Span span) { - if (this.pitch == 0) + if (this.Pitch == 0) { #if SPAN_RUNTIME_SUPPORT // We can only create a Span if the buffer is contiguous @@ -817,7 +817,7 @@ public bool TryGetSpan(out Span span) return true; #else // An empty Span2D is still valid - if (this.instance is null) + if (this.Instance is null) { span = default; @@ -825,9 +825,9 @@ public bool TryGetSpan(out Span span) } // Without Span runtime support, we can only get a Span from a T[] instance - if (this.instance.GetType() == typeof(T[])) + if (this.Instance.GetType() == typeof(T[])) { - span = Unsafe.As(this.instance).AsSpan((int)this.offset, Size); + span = Unsafe.As(this.Instance).AsSpan((int)this.Offset, Size); return true; } @@ -906,12 +906,12 @@ public override string ToString() #if SPAN_RUNTIME_SUPPORT left.span == right.span && #else - ReferenceEquals(left.instance, right.instance) && - left.offset == right.offset && + ReferenceEquals(left.Instance, right.Instance) && + left.Offset == right.Offset && left.height == right.height && #endif left.width == right.width && - left.pitch == right.pitch; + left.Pitch == right.Pitch; } /// From c10665a4357716057819443e41720e90b4337de1 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 22:33:52 +0200 Subject: [PATCH 055/200] Fixed some leftover changes --- .../Memory/ReadOnlySpan2D{T}.Enumerator.cs | 8 ++++---- .../Memory/ReadOnlySpan2D{T}.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs index c766cd7cc1d..586d1044d7c 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs @@ -37,7 +37,7 @@ public ReadOnlyRefEnumerable GetRow(int row) ref T r1 = ref Unsafe.Add(ref r0, (this.width + this.pitch) * row); #if SPAN_RUNTIME_SUPPORT - return new RefEnumerable(ref r1, Width, 1); + return new ReadOnlyRefEnumerable(r1, Width, 1); #else IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); @@ -64,7 +64,7 @@ public ReadOnlyRefEnumerable GetColumn(int column) ref T r1 = ref Unsafe.Add(ref r0, column); #if SPAN_RUNTIME_SUPPORT - return new ReadOnlyRefEnumerable(ref r1, Height, this.width + this.Pitch); + return new ReadOnlyRefEnumerable(r1, Height, this.width + this.pitch); #else IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); @@ -89,10 +89,10 @@ public ref struct Enumerator { #if SPAN_RUNTIME_SUPPORT /// - /// The instance pointing to the first item in the target memory area. + /// The instance pointing to the first item in the target memory area. /// /// Just like in , the length is the height of the 2D region. - private readonly Span span; + private readonly ReadOnlySpan span; #else /// /// The target instance, if present. diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 6478e01dbb8..caa8f731533 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -557,7 +557,7 @@ public void CopyTo(Span2D destination) GetRowSpan(i).CopyTo(destination.GetRowSpan(i)); } #else - Enumerator destinationEnumerator = destination.GetEnumerator(); + Span2D.Enumerator destinationEnumerator = destination.GetEnumerator(); // Fallback path with two enumerators foreach (T item in this) From 7755850284e109d3337bc80c12b8af8c47456191 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 18 Jun 2020 22:53:51 +0200 Subject: [PATCH 056/200] Added ReadOnlySpan2D tests --- .../Memory/ReadOnlyMemory2D{T}.cs | 4 +- .../Memory/Test_ReadOnlySpan2D{T}.cs | 735 ++++++++++++++++++ ...UnitTests.HighPerformance.Shared.projitems | 1 + 3 files changed, 738 insertions(+), 2 deletions(-) create mode 100644 UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 7ace6a0df9e..47f2039097c 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -374,14 +374,14 @@ public ReadOnlySpan2D Span // If the wrapped object is a ReadOnlyMemory, it is always pre-offset ref T r0 = ref memory.Span.DangerousGetReference(); - return new ReadOnlySpan2D(ref r0, this.height, this.width, this.pitch); + return new ReadOnlySpan2D(r0, this.height, this.width, this.pitch); } else { // The only other possible cases is with the instance being an array ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset); - return new ReadOnlySpan2D(ref r0, this.height, this.width, this.pitch); + return new ReadOnlySpan2D(r0, this.height, this.width, this.pitch); } #else return new ReadOnlySpan2D(this.instance, this.offset, this.height, this.width, this.pitch); diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs new file mode 100644 index 00000000000..0e1b4c2dcea --- /dev/null +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs @@ -0,0 +1,735 @@ +// 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.CodeAnalysis; +using System.Runtime.CompilerServices; +using Microsoft.Toolkit.HighPerformance.Enumerables; +using Microsoft.Toolkit.HighPerformance.Memory; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.HighPerformance.Memory +{ + [TestClass] + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")] + public class Test_ReadOnlySpan2DT + { + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_Empty() + { + ReadOnlySpan2D empty1 = default; + + Assert.IsTrue(empty1.IsEmpty); + Assert.AreEqual(empty1.Size, 0); + Assert.AreEqual(empty1.Width, 0); + Assert.AreEqual(empty1.Height, 0); + + ReadOnlySpan2D empty2 = ReadOnlySpan2D.Empty; + + Assert.IsTrue(empty2.IsEmpty); + Assert.AreEqual(empty2.Size, 0); + Assert.AreEqual(empty2.Width, 0); + Assert.AreEqual(empty2.Height, 0); + } + +#if !WINDOWS_UWP + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public unsafe void Test_ReadOnlySpan2DT_RefConstructor() + { + ReadOnlySpan span = stackalloc[] + { + 1, 2, 3, 4, 5, 6 + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(span[0], 2, 3, 0); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Width, 3); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d[0, 0], 1); + Assert.AreEqual(span2d[1, 2], 6); + + Assert.ThrowsException(() => new ReadOnlySpan2D(Unsafe.AsRef(null), -1, 0, 0)); + Assert.ThrowsException(() => new ReadOnlySpan2D(Unsafe.AsRef(null), 1, -2, 0)); + Assert.ThrowsException(() => new ReadOnlySpan2D(Unsafe.AsRef(null), 1, 0, -5)); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public unsafe void Test_ReadOnlySpan2DT_PtrConstructor() + { + int* ptr = stackalloc[] + { + 1, 2, 3, 4, 5, 6 + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(ptr, 2, 3, 0); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Width, 3); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d[0, 0], 1); + Assert.AreEqual(span2d[1, 2], 6); + + Assert.ThrowsException(() => new ReadOnlySpan2D((void*)0, -1, 0, 0)); + Assert.ThrowsException(() => new ReadOnlySpan2D((void*)0, 1, -2, 0)); + Assert.ThrowsException(() => new ReadOnlySpan2D((void*)0, 1, 0, -5)); + } +#endif + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_Array1DConstructor() + { + int[] array = + { + 1, 2, 3, 4, 5, 6 + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 1, 2, 2, 1); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d[0, 0], 2); + Assert.AreEqual(span2d[1, 1], 6); + + Assert.ThrowsException(() => new ReadOnlySpan2D(new string[1], 1, 1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, -99, 1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 0, -10, 1, 1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 0, 1, 1, -1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 0, 1, -100, 1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 0, 10, 1, 120)); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_Array2DConstructor_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Width, 3); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d[0, 1], 2); + Assert.AreEqual(span2d[1, 2], 6); + + Assert.ThrowsException(() => new ReadOnlySpan2D(new string[1, 2])); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_Array2DConstructor_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 0, 1, 2, 2); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d[0, 0], 2); + Assert.AreEqual(span2d[1, 1], 6); + + Assert.ThrowsException(() => new ReadOnlySpan2D(new string[1, 2], 0, 0, 2, 2)); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_Array3DConstructor_1() + { + int[,,] array = + { + { + { 1, 2, 3 }, + { 4, 5, 6 } + }, + { + { 10, 20, 30 }, + { 40, 50, 60 } + } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 1); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Width, 3); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d[0, 0], 10); + Assert.AreEqual(span2d[0, 1], 20); + Assert.AreEqual(span2d[1, 2], 60); + + Assert.ThrowsException(() => new ReadOnlySpan2D(array, -1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 20)); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_Array3DConstructor_2() + { + int[,,] array = + { + { + { 1, 2, 3 }, + { 4, 5, 6 } + }, + { + { 10, 20, 30 }, + { 40, 50, 60 } + } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 1, 0, 1, 2, 2); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d[0, 0], 20); + Assert.AreEqual(span2d[0, 1], 30); + Assert.AreEqual(span2d[1, 1], 60); + + Assert.ThrowsException(() => new ReadOnlySpan2D(array, -1, 1, 1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 1, -1, 1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 1, 1, -1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 1, 1, 1, -1, 1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 1, 1, 1, 1, -1)); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_CopyTo_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + int[] target = new int[array.Length]; + + span2d.CopyTo(target); + + CollectionAssert.AreEqual(array, target); + + Assert.ThrowsException(() => new ReadOnlySpan2D(array).CopyTo(Span.Empty)); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_CopyTo_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 0, 1, 2, 2); + + int[] target = new int[4]; + + span2d.CopyTo(target); + + int[] expected = { 2, 3, 5, 6 }; + + CollectionAssert.AreEqual(target, expected); + + Assert.ThrowsException(() => new ReadOnlySpan2D(array).CopyTo(Span.Empty)); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_CopyTo2D_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + int[,] target = new int[2, 3]; + + span2d.CopyTo(target); + + CollectionAssert.AreEqual(array, target); + + Assert.ThrowsException(() => new ReadOnlySpan2D(array).CopyTo(Span2D.Empty)); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_CopyTo2D_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 0, 1, 2, 2); + + int[,] target = new int[2, 2]; + + span2d.CopyTo(target); + + int[,] expected = + { + { 2, 3 }, + { 5, 6 } + }; + + CollectionAssert.AreEqual(target, expected); + + Assert.ThrowsException(() => new ReadOnlySpan2D(array).CopyTo(new Span2D(target))); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_TryCopyTo() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + int[] target = new int[array.Length]; + + Assert.IsTrue(span2d.TryCopyTo(target)); + Assert.IsFalse(span2d.TryCopyTo(Span.Empty)); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_TryCopyTo2D() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + int[,] target = new int[2, 3]; + + Assert.IsTrue(span2d.TryCopyTo(target)); + Assert.IsFalse(span2d.TryCopyTo(Span2D.Empty)); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public unsafe void Test_ReadOnlySpan2DT_GetPinnableReference() + { + Assert.IsTrue(Unsafe.AreSame( + ref Unsafe.AsRef(null), + ref ReadOnlySpan2D.Empty.GetPinnableReference())); + + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + ref int r0 = ref span2d.GetPinnableReference(); + + Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0])); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public unsafe void Test_ReadOnlySpan2DT_DangerousGetReference() + { + Assert.IsTrue(Unsafe.AreSame( + ref Unsafe.AsRef(null), + ref ReadOnlySpan2D.Empty.DangerousGetReference())); + + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + ref int r0 = ref span2d.DangerousGetReference(); + + Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0])); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_Slice_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + ReadOnlySpan2D slice1 = span2d.Slice(1, 1, 2, 1); + + Assert.AreEqual(slice1.Size, 2); + Assert.AreEqual(slice1.Height, 1); + Assert.AreEqual(slice1.Width, 2); + Assert.AreEqual(slice1[0, 0], 5); + Assert.AreEqual(slice1[0, 1], 6); + + ReadOnlySpan2D slice2 = span2d.Slice(0, 1, 2, 2); + + Assert.AreEqual(slice2.Size, 4); + Assert.AreEqual(slice2.Height, 2); + Assert.AreEqual(slice2.Width, 2); + Assert.AreEqual(slice2[0, 0], 2); + Assert.AreEqual(slice2[1, 0], 5); + Assert.AreEqual(slice2[1, 1], 6); + + Assert.ThrowsException(() => new ReadOnlySpan2D(array).Slice(-1, 1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array).Slice(1, -1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array).Slice(1, 1, -1, 1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array).Slice(1, 1, 1, -1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array).Slice(10, 1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array).Slice(1, 12, 12, 1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array).Slice(1, 1, 1, 55)); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_Slice_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + ReadOnlySpan2D slice1 = span2d.Slice(0, 0, 2, 2); + + Assert.AreEqual(slice1.Size, 4); + Assert.AreEqual(slice1.Height, 2); + Assert.AreEqual(slice1.Width, 2); + Assert.AreEqual(slice1[0, 0], 1); + Assert.AreEqual(slice1[1, 1], 5); + + ReadOnlySpan2D slice2 = slice1.Slice(1, 0, 2, 1); + + Assert.AreEqual(slice2.Size, 2); + Assert.AreEqual(slice2.Height, 1); + Assert.AreEqual(slice2.Width, 2); + Assert.AreEqual(slice2[0, 0], 4); + Assert.AreEqual(slice2[0, 1], 5); + + ReadOnlySpan2D slice3 = slice2.Slice(0, 1, 1, 1); + + Assert.AreEqual(slice3.Size, 1); + Assert.AreEqual(slice3.Height, 1); + Assert.AreEqual(slice3.Width, 1); + Assert.AreEqual(slice3[0, 0], 5); + } + +#if !WINDOWS_UWP + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_GetRowReadOnlySpan() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + ReadOnlySpan span = span2d.GetRowSpan(1); + + Assert.IsTrue(Unsafe.AreSame( + ref Unsafe.AsRef(span[0]), + ref array[1, 0])); + Assert.IsTrue(Unsafe.AreSame( + ref Unsafe.AsRef(span[2]), + ref array[1, 2])); + + Assert.ThrowsException(() => new ReadOnlySpan2D(array).GetRowSpan(-1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array).GetRowSpan(5)); + } +#endif + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_TryGetReadOnlySpan_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + bool success = span2d.TryGetSpan(out ReadOnlySpan span); + + Assert.IsTrue(success); + Assert.AreEqual(span.Length, span2d.Size); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_TryGetReadOnlySpan_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 0, 0, 2, 2); + + bool success = span2d.TryGetSpan(out ReadOnlySpan span); + + Assert.IsFalse(success); + Assert.IsTrue(span.IsEmpty); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_ToArray_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + int[,] copy = span2d.ToArray(); + + Assert.AreEqual(copy.GetLength(0), array.GetLength(0)); + Assert.AreEqual(copy.GetLength(1), array.GetLength(1)); + + CollectionAssert.AreEqual(array, copy); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_ToArray_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 0, 0, 2, 2); + + int[,] copy = span2d.ToArray(); + + Assert.AreEqual(copy.GetLength(0), 2); + Assert.AreEqual(copy.GetLength(1), 2); + + int[,] expected = + { + { 1, 2 }, + { 4, 5 } + }; + + CollectionAssert.AreEqual(expected, copy); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Test_ReadOnlySpan2DT_Equals() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + _ = span2d.Equals(null); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Test_ReadOnlySpan2DT_GetHashCode() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + _ = span2d.GetHashCode(); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_ToString() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); + + string text = span2d.ToString(); + + const string expected = "Microsoft.Toolkit.HighPerformance.Memory.ReadOnlySpan2D[2, 3]"; + + Assert.AreEqual(text, expected); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_opEquals() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d_1 = new ReadOnlySpan2D(array); + ReadOnlySpan2D span2d_2 = new ReadOnlySpan2D(array); + + Assert.IsTrue(span2d_1 == span2d_2); + Assert.IsFalse(span2d_1 == ReadOnlySpan2D.Empty); + Assert.IsTrue(ReadOnlySpan2D.Empty == ReadOnlySpan2D.Empty); + + ReadOnlySpan2D span2d_3 = new ReadOnlySpan2D(array, 0, 0, 2, 2); + + Assert.IsFalse(span2d_1 == span2d_3); + Assert.IsFalse(span2d_3 == ReadOnlySpan2D.Empty); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_ImplicitCast() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlySpan2D span2d_1 = array; + ReadOnlySpan2D span2d_2 = new ReadOnlySpan2D(array); + + Assert.IsTrue(span2d_1 == span2d_2); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_GetRow() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlyRefEnumerable enumerable = new ReadOnlySpan2D(array).GetRow(1); + + int[] expected = { 4, 5, 6 }; + + CollectionAssert.AreEqual(enumerable.ToArray(), expected); + + Assert.ThrowsException(() => new ReadOnlySpan2D(array).GetRow(-1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array).GetRow(2)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array).GetRow(1000)); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_GetColumn() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlyRefEnumerable enumerable = new ReadOnlySpan2D(array).GetColumn(2); + + int[] expected = { 3, 6 }; + + CollectionAssert.AreEqual(enumerable.ToArray(), expected); + + Assert.ThrowsException(() => new ReadOnlySpan2D(array).GetColumn(-1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array).GetColumn(3)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array).GetColumn(1000)); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_GetEnumerator() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + int[] result = new int[4]; + int i = 0; + + foreach (var item in new ReadOnlySpan2D(array, 0, 1, 2, 2)) + { + result[i++] = item; + } + + int[] expected = { 2, 3, 5, 6 }; + + CollectionAssert.AreEqual(result, expected); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_GetEnumerator_Empty() + { + var enumerator = ReadOnlySpan2D.Empty.GetEnumerator(); + + Assert.IsFalse(enumerator.MoveNext()); + } + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems index 61eb927075b..3d9500684f7 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems +++ b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems @@ -39,6 +39,7 @@ + From 1dc46f139d816cd7199db42cf015a8772a4847e6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 00:24:24 +0200 Subject: [PATCH 057/200] Inverted height/width parameters for 2D memory types --- .../Extensions/ArrayExtensions.2D.cs | 13 +++--- .../Memory/Memory2D{T}.cs | 32 +++++++------- .../Memory/ReadOnlySpan2D{T}.cs | 42 +++++++++---------- .../Memory/Span2D{T}.cs | 42 +++++++++---------- 4 files changed, 65 insertions(+), 64 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 434a5fa39af..191b5ea8167 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -124,6 +124,7 @@ private sealed class RawArray2DData /// The column to start on (inclusive, 0-based index). /// The positive width of area to fill. /// The positive height of area to fill. + [Obsolete("Use array.AsSpan2D(...).Fill(...) instead")] 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)); @@ -252,8 +253,8 @@ public static Span2D AsSpan2D(this T[,] array) /// The input 2D array instance. /// The target row to map within . /// The target column to map within . - /// The width to map within . /// The height to map within . + /// The width to map within . /// Thrown when is . /// /// Thrown when doesn't match . @@ -265,14 +266,14 @@ public static Span2D AsSpan2D(this T[,] array) /// A instance with the values of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span2D AsSpan2D(this T[,] array, int row, int column, int width, int height) + public static Span2D AsSpan2D(this T[,] array, int row, int column, int height, int width) { if (array is null) { throw new NullReferenceException(); } - return new Span2D(array, row, column, width, height); + return new Span2D(array, row, column, height, width); } /// @@ -301,8 +302,8 @@ public static Memory2D AsMemory2D(this T[,] array) /// The input 2D array instance. /// The target row to map within . /// The target column to map within . - /// The width to map within . /// The height to map within . + /// The width to map within . /// Thrown when is . /// /// Thrown when doesn't match . @@ -314,14 +315,14 @@ public static Memory2D AsMemory2D(this T[,] array) /// A instance with the values of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Memory2D AsMemory2D(this T[,] array, int row, int column, int width, int height) + public static Memory2D AsMemory2D(this T[,] array, int row, int column, int height, int width) { if (array is null) { throw new NullReferenceException(); } - return new Memory2D(array, row, column, width, height); + return new Memory2D(array, row, column, height, width); } #if SPAN_RUNTIME_SUPPORT diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 0187edff285..676bfd20e47 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -52,8 +52,8 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// /// The target array to wrap. /// The initial offset within . - /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. /// /// Thrown when doesn't match . /// @@ -61,8 +61,8 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// Thrown when either , or are invalid. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory2D(T[] array, int offset, int width, int height) - : this(array, offset, width, height, 0) + public Memory2D(T[] array, int offset, int height, int width) + : this(array, offset, height, width, 0) { } @@ -71,8 +71,8 @@ public Memory2D(T[] array, int offset, int width, int height) /// /// The target array to wrap. /// The initial offset within . - /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. /// The pitch in the resulting 2D area. /// /// Thrown when doesn't match . @@ -82,7 +82,7 @@ public Memory2D(T[] array, int offset, int width, int height) /// or are invalid. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory2D(T[] array, int offset, int width, int height, int pitch) + public Memory2D(T[] array, int offset, int height, int width, int pitch) { if (array.IsCovariant()) { @@ -143,8 +143,8 @@ public Memory2D(T[,]? array) /// The given 2D array to wrap. /// The target row to map within . /// The target column to map within . - /// The width to map within . /// The height to map within . + /// The width to map within . /// /// Thrown when doesn't match . /// @@ -153,7 +153,7 @@ public Memory2D(T[,]? array) /// are negative or not within the bounds that are valid for . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory2D(T[,]? array, int row, int column, int width, int height) + public Memory2D(T[,]? array, int row, int column, int height, int width) { if (array is null) { @@ -186,14 +186,14 @@ public Memory2D(T[,]? array, int row, int column, int width, int height) ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } - if (width > (columns - column)) + if (height > (rows - row)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - if (height > (rows - row)) + if (width > (columns - column)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } this.instance = array; @@ -238,14 +238,14 @@ public Memory2D(T[,,] array, int depth) /// /// The target to wrap. /// The initial offset within . - /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. /// /// Thrown when either , or are invalid. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory2D(Memory memory, int offset, int width, int height) - : this(memory, offset, width, height, 0) + public Memory2D(Memory memory, int offset, int height, int width) + : this(memory, offset, height, width, 0) { } @@ -254,15 +254,15 @@ public Memory2D(Memory memory, int offset, int width, int height) /// /// The target to wrap. /// The initial offset within . - /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. /// The pitch in the resulting 2D area. /// /// Thrown when either , , /// or are invalid. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory2D(Memory memory, int offset, int width, int height, int pitch) + public Memory2D(Memory memory, int offset, int height, int width, int pitch) { if ((uint)offset >= (uint)memory.Length) { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index caa8f731533..4e61c440a3e 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -138,8 +138,8 @@ internal ReadOnlySpan2D(object instance, IntPtr offset, int height, int width, i /// Initializes a new instance of the struct. /// /// The target array to wrap. - /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. /// /// Thrown when doesn't match . /// @@ -148,8 +148,8 @@ internal ReadOnlySpan2D(object instance, IntPtr offset, int height, int width, i /// /// The total area must match the lenght of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan2D(T[] array, int width, int height) - : this(array, 0, width, height, 0) + public ReadOnlySpan2D(T[] array, int height, int width) + : this(array, 0, height, width, 0) { } @@ -158,8 +158,8 @@ public ReadOnlySpan2D(T[] array, int width, int height) /// /// The target array to wrap. /// The initial offset within . - /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. /// The pitch in the resulting 2D area. /// /// Thrown when doesn't match . @@ -171,7 +171,7 @@ public ReadOnlySpan2D(T[] array, int width, int height) /// Thrown when the requested area is outside of bounds for . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan2D(T[] array, int offset, int width, int height, int pitch) + public ReadOnlySpan2D(T[] array, int offset, int height, int width, int pitch) { if (array.IsCovariant()) { @@ -183,14 +183,14 @@ public ReadOnlySpan2D(T[] array, int offset, int width, int height, int pitch) ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); } - if (width < 0) + if (height < 0) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - if (height < 0) + if (width < 0) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } if (pitch < 0) @@ -263,8 +263,8 @@ public ReadOnlySpan2D(T[,]? array) /// The given 2D array to wrap. /// The target row to map within . /// The target column to map within . - /// The width to map within . /// The height to map within . + /// The width to map within . /// /// Thrown when doesn't match . /// @@ -272,7 +272,7 @@ public ReadOnlySpan2D(T[,]? array) /// Thrown when either , or /// are negative or not within the bounds that are valid for . /// - public ReadOnlySpan2D(T[,] array, int row, int column, int width, int height) + public ReadOnlySpan2D(T[,] array, int row, int column, int height, int width) { if (array.IsCovariant()) { @@ -293,14 +293,14 @@ public ReadOnlySpan2D(T[,] array, int row, int column, int width, int height) ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } - if ((uint)width > (uint)(columns - column)) + if ((uint)height > (uint)(rows - row)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - if ((uint)height > (uint)(rows - row)) + if ((uint)width > (uint)(columns - column)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } #if SPAN_RUNTIME_SUPPORT @@ -354,14 +354,14 @@ public ReadOnlySpan2D(T[,,] array, int depth) /// The target layer to map within . /// The target row to map within . /// The target column to map within . - /// The width to map within . /// The height to map within . + /// The width to map within . /// /// Thrown when doesn't match . /// /// Thrown when a parameter is invalid. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan2D(T[,,] array, int depth, int row, int column, int width, int height) + public ReadOnlySpan2D(T[,,] array, int depth, int row, int column, int height, int width) { if (array.IsCovariant()) { @@ -387,14 +387,14 @@ public ReadOnlySpan2D(T[,,] array, int depth, int row, int column, int width, in ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } - if ((uint)width > (uint)(columns - column)) + if ((uint)height > (uint)(rows - row)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - if ((uint)height > (uint)(rows - row)) + if ((uint)width > (uint)(columns - column)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } #if SPAN_RUNTIME_SUPPORT diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index ac63a0e2935..26959529bea 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -167,8 +167,8 @@ internal Span2D(object instance, IntPtr offset, int height, int width, int pitch /// Initializes a new instance of the struct. /// /// The target array to wrap. - /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. /// /// Thrown when doesn't match . /// @@ -177,8 +177,8 @@ internal Span2D(object instance, IntPtr offset, int height, int width, int pitch /// /// The total area must match the lenght of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span2D(T[] array, int width, int height) - : this(array, 0, width, height, 0) + public Span2D(T[] array, int height, int width) + : this(array, 0, height, width, 0) { } @@ -187,8 +187,8 @@ public Span2D(T[] array, int width, int height) /// /// The target array to wrap. /// The initial offset within . - /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. /// The pitch in the resulting 2D area. /// /// Thrown when doesn't match . @@ -200,7 +200,7 @@ public Span2D(T[] array, int width, int height) /// Thrown when the requested area is outside of bounds for . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span2D(T[] array, int offset, int width, int height, int pitch) + public Span2D(T[] array, int offset, int height, int width, int pitch) { if (array.IsCovariant()) { @@ -212,14 +212,14 @@ public Span2D(T[] array, int offset, int width, int height, int pitch) ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); } - if (width < 0) + if (height < 0) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - if (height < 0) + if (width < 0) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } if (pitch < 0) @@ -292,8 +292,8 @@ public Span2D(T[,]? array) /// The given 2D array to wrap. /// The target row to map within . /// The target column to map within . - /// The width to map within . /// The height to map within . + /// The width to map within . /// /// Thrown when doesn't match . /// @@ -301,7 +301,7 @@ public Span2D(T[,]? array) /// Thrown when either , or /// are negative or not within the bounds that are valid for . /// - public Span2D(T[,] array, int row, int column, int width, int height) + public Span2D(T[,] array, int row, int column, int height, int width) { if (array.IsCovariant()) { @@ -322,14 +322,14 @@ public Span2D(T[,] array, int row, int column, int width, int height) ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } - if ((uint)width > (uint)(columns - column)) + if ((uint)height > (uint)(rows - row)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - if ((uint)height > (uint)(rows - row)) + if ((uint)width > (uint)(columns - column)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } #if SPAN_RUNTIME_SUPPORT @@ -383,14 +383,14 @@ public Span2D(T[,,] array, int depth) /// The target layer to map within . /// The target row to map within . /// The target column to map within . - /// The width to map within . /// The height to map within . + /// The width to map within . /// /// Thrown when doesn't match . /// /// Thrown when a parameter is invalid. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span2D(T[,,] array, int depth, int row, int column, int width, int height) + public Span2D(T[,,] array, int depth, int row, int column, int height, int width) { if (array.IsCovariant()) { @@ -416,14 +416,14 @@ public Span2D(T[,,] array, int depth, int row, int column, int width, int height ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } - if ((uint)width > (uint)(columns - column)) + if ((uint)height > (uint)(rows - row)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - if ((uint)height > (uint)(rows - row)) + if ((uint)width > (uint)(columns - column)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } #if SPAN_RUNTIME_SUPPORT From 28360b9d7d5c01bfa9b31838af5f63338b24cd9e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 00:33:32 +0200 Subject: [PATCH 058/200] Updated Memory2D constructors --- .../Memory/Memory2D{T}.cs | 169 ++++++++++++------ .../Memory/Span2D{T}.cs | 4 +- 2 files changed, 120 insertions(+), 53 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 676bfd20e47..18c5c551c4d 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -51,18 +51,18 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// Initializes a new instance of the struct. /// /// The target array to wrap. - /// The initial offset within . /// The height of the resulting 2D area. /// The width of each row in the resulting 2D area. /// /// Thrown when doesn't match . /// - /// - /// Thrown when either , or are invalid. + /// + /// Thrown when either or are invalid. /// + /// The total area must match the lenght of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory2D(T[] array, int offset, int height, int width) - : this(array, offset, height, width, 0) + public Memory2D(T[] array, int height, int width) + : this(array, 0, height, width, 0) { } @@ -78,8 +78,10 @@ public Memory2D(T[] array, int offset, int height, int width) /// Thrown when doesn't match . /// /// - /// Thrown when either , , - /// or are invalid. + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(T[] array, int offset, int height, int width, int pitch) @@ -89,14 +91,38 @@ public Memory2D(T[] array, int offset, int height, int width, int pitch) ThrowHelper.ThrowArrayTypeMismatchException(); } - if ((uint)offset >= (uint)array.Length) + if ((uint)offset > (uint)array.Length) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); } - int remaining = array.Length - offset; + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + if (width == 0 || height == 0) + { + this = default; + + return; + } + + int + remaining = array.Length - offset, + area = ((width + pitch) * (height - 1)) + width; - if ((((uint)width + (uint)pitch) * (uint)height) > (uint)remaining) + if (area > remaining) { ThrowHelper.ThrowArgumentException(); } @@ -109,13 +135,12 @@ public Memory2D(T[] array, int offset, int height, int width, int pitch) } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct wrapping a 2D array. /// - /// The target array to wrap. + /// The given 2D array to wrap. /// /// Thrown when doesn't match . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(T[,]? array) { if (array is null) @@ -131,7 +156,7 @@ public Memory2D(T[,]? array) } this.instance = array; - this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReference()); + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(0, 0)); this.height = array.GetLength(0); this.width = array.GetLength(1); this.pitch = 0; @@ -152,21 +177,8 @@ public Memory2D(T[,]? array) /// Thrown when either , or /// are negative or not within the bounds that are valid for . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory2D(T[,]? array, int row, int column, int height, int width) + public Memory2D(T[,] array, int row, int column, int height, int width) { - if (array is null) - { - if ((row | column | width | height) != 0) - { - ThrowHelper.ThrowArgumentException(); - } - - this = default; - - return; - } - if (array.IsCovariant()) { ThrowHelper.ThrowArrayTypeMismatchException(); @@ -186,21 +198,21 @@ public Memory2D(T[,]? array, int row, int column, int height, int width) ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } - if (height > (rows - row)) + if ((uint)height > (uint)(rows - row)) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - if (width > (columns - column)) + if ((uint)width > (uint)(columns - column)) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } this.instance = array; this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column)); - this.height = height; + this.height = array.GetLength(0); this.width = width; - this.pitch = row + (array.GetLength(1) - column); + this.pitch = columns - width; } /// @@ -211,7 +223,7 @@ public Memory2D(T[,]? array, int row, int column, int height, int width) /// /// Thrown when doesn't match . /// - /// Thrown when either is invalid. + /// Thrown when a parameter is invalid. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(T[,,] array, int depth) { @@ -237,15 +249,15 @@ public Memory2D(T[,,] array, int depth) /// Initializes a new instance of the struct. /// /// The target to wrap. - /// The initial offset within . /// The height of the resulting 2D area. /// The width of each row in the resulting 2D area. - /// - /// Thrown when either , or are invalid. + /// + /// Thrown when either or are invalid. /// + /// The total area must match the lenght of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory2D(Memory memory, int offset, int height, int width) - : this(memory, offset, height, width, 0) + public Memory2D(Memory memory, int height, int width) + : this(memory, 0, height, width, 0) { } @@ -258,20 +270,46 @@ public Memory2D(Memory memory, int offset, int height, int width) /// The width of each row in the resulting 2D area. /// The pitch in the resulting 2D area. /// - /// Thrown when either , , - /// or are invalid. + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(Memory memory, int offset, int height, int width, int pitch) { - if ((uint)offset >= (uint)memory.Length) + if ((uint)offset > (uint)memory.Length) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); } - int remaining = memory.Length - offset; + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + if (width == 0 || height == 0) + { + this = default; + + return; + } + + int + remaining = memory.Length - offset, + area = ((width + pitch) * (height - 1)) + width; - if ((((uint)width + (uint)pitch) * (uint)height) > (uint)remaining) + if (area > remaining) { ThrowHelper.ThrowArgumentException(); } @@ -317,12 +355,30 @@ public bool IsEmpty } /// - /// Gets the length of the current instance. + /// Gets the length of the current instance. /// - public int Length + public int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.height * this.width; + get => Height * Width; + } + + /// + /// Gets the height of the underlying 2D memory area. + /// + public int Height + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.height; + } + + /// + /// Gets the width of the underlying 2D memory area. + /// + public int Width + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.width; } /// @@ -368,7 +424,7 @@ public Span2D Span /// The target column to map within the current instance. /// The width to map within the current instance. /// The height to map within the current instance. - /// + /// /// Thrown when either , or /// are negative or not within the bounds that are valid for the current instance. /// @@ -376,7 +432,7 @@ public Span2D Span [Pure] public unsafe Memory2D Slice(int row, int column, int width, int height) { - if ((uint)row >= this.height) + if ((uint)row >= Height) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); } @@ -391,15 +447,26 @@ public unsafe Memory2D Slice(int row, int column, int width, int height) ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } - if ((uint)height > (this.height - row)) + if ((uint)height > (Height - row)) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - int shift = ((this.width + this.pitch) * row) + column; + int + shift = ((this.width + this.pitch) * row) + column, + pitch = this.pitch + (this.width - width); + IntPtr offset = (IntPtr)((byte*)this.offset + shift); - return new Memory2D(this.instance!, offset, height, width, this.pitch); + if (this.instance is Memory memory) + { + // Memory instance are always already sliced + object instance = memory.Slice((int)offset); + + return new Memory2D(instance, default, height, width, pitch); + } + + return new Memory2D(this.instance!, offset, height, width, pitch); } /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 26959529bea..6a4c6dcd7b8 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -30,8 +30,8 @@ public readonly ref partial struct Span2D // // reference__ _________width_________ ________... // \/ \/ - // | -- | -- | |- | -- | -- | -- | -- | -- | -- | -- | - // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- |_ + // | -- | -- | |- | -- | -- | -- | -- | -- | -- | -- |_ + // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | | // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | | // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |_height // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- |_| From 568c9934e1c1a82fa122eeac66e54624fc2933cf Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 00:35:50 +0200 Subject: [PATCH 059/200] Reordered params in Slice APIs --- .../Memory/Memory2D{T}.cs | 12 ++++++------ .../Memory/Span2D{T}.cs | 12 ++++++------ .../Memory/Test_Span2D{T}.cs | 10 +++++----- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 18c5c551c4d..fc35fbbe6aa 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -422,15 +422,15 @@ public Span2D Span /// /// The target row to map within the current instance. /// The target column to map within the current instance. - /// The width to map within the current instance. /// The height to map within the current instance. + /// The width to map within the current instance. /// /// Thrown when either , or /// are negative or not within the bounds that are valid for the current instance. /// /// A new instance representing a slice of the current one. [Pure] - public unsafe Memory2D Slice(int row, int column, int width, int height) + public unsafe Memory2D Slice(int row, int column, int height, int width) { if ((uint)row >= Height) { @@ -442,14 +442,14 @@ public unsafe Memory2D Slice(int row, int column, int width, int height) ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } - if ((uint)width > (this.width - column)) + if ((uint)height > (Height - row)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - if ((uint)height > (Height - row)) + if ((uint)width > (this.width - column)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } int diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 6a4c6dcd7b8..3982f8303d4 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -730,15 +730,15 @@ public ref T DangerousGetReference() /// /// The target row to map within the current instance. /// The target column to map within the current instance. - /// The width to map within the current instance. /// The height to map within the current instance. + /// The width to map within the current instance. /// /// Thrown when either , or /// are negative or not within the bounds that are valid for the current instance. /// /// A new instance representing a slice of the current one. [Pure] - public Span2D Slice(int row, int column, int width, int height) + public Span2D Slice(int row, int column, int height, int width) { if ((uint)row >= Height) { @@ -750,14 +750,14 @@ public Span2D Slice(int row, int column, int width, int height) ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } - if ((uint)width > (this.width - column)) + if ((uint)height > (Height - row)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - if ((uint)height > (Height - row)) + if ((uint)width > (this.width - column)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } int diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs index c0caa4a54cc..35ef0a515f2 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -480,7 +480,7 @@ public void Test_Span2DT_Slice_1() Span2D span2d = new Span2D(array); - Span2D slice1 = span2d.Slice(1, 1, 2, 1); + Span2D slice1 = span2d.Slice(1, 1, 1, 2); Assert.AreEqual(slice1.Size, 2); Assert.AreEqual(slice1.Height, 1); @@ -499,11 +499,11 @@ public void Test_Span2DT_Slice_1() Assert.ThrowsException(() => new Span2D(array).Slice(-1, 1, 1, 1)); Assert.ThrowsException(() => new Span2D(array).Slice(1, -1, 1, 1)); - Assert.ThrowsException(() => new Span2D(array).Slice(1, 1, -1, 1)); Assert.ThrowsException(() => new Span2D(array).Slice(1, 1, 1, -1)); + Assert.ThrowsException(() => new Span2D(array).Slice(1, 1, -1, 1)); Assert.ThrowsException(() => new Span2D(array).Slice(10, 1, 1, 1)); - Assert.ThrowsException(() => new Span2D(array).Slice(1, 12, 12, 1)); - Assert.ThrowsException(() => new Span2D(array).Slice(1, 1, 1, 55)); + Assert.ThrowsException(() => new Span2D(array).Slice(1, 12, 1, 12)); + Assert.ThrowsException(() => new Span2D(array).Slice(1, 1, 55, 1)); } [TestCategory("Span2DT")] @@ -526,7 +526,7 @@ public void Test_Span2DT_Slice_2() Assert.AreEqual(slice1[0, 0], 1); Assert.AreEqual(slice1[1, 1], 5); - Span2D slice2 = slice1.Slice(1, 0, 2, 1); + Span2D slice2 = slice1.Slice(1, 0, 1, 2); Assert.AreEqual(slice2.Size, 2); Assert.AreEqual(slice2.Height, 1); From 26441a2613e609e96be0085138ec0bef4aaedf20 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 01:18:27 +0200 Subject: [PATCH 060/200] Added missing Memory2D constructor --- .../Memory/Memory2D{T}.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index fc35fbbe6aa..64f3ca4cd1e 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -244,6 +244,63 @@ public Memory2D(T[,,] array, int depth) this.pitch = 0; } + /// + /// Initializes a new instance of the struct wrapping a layer in a 3D array. + /// + /// The given 3D array to wrap. + /// The target layer to map within . + /// The target row to map within . + /// The target column to map within . + /// The height to map within . + /// The width to map within . + /// + /// Thrown when doesn't match . + /// + /// Thrown when a parameter is invalid. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory2D(T[,,] array, int depth, int row, int column, int height, int width) + { + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } + + if ((uint)depth >= (uint)array.GetLength(0)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); + } + + int + rows = array.GetLength(1), + columns = array.GetLength(2); + + if ((uint)row >= (uint)rows) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + if ((uint)column >= (uint)columns) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + if ((uint)height > (uint)(rows - row)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if ((uint)width > (uint)(columns - column)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, row, column)); + this.height = height; + this.width = width; + this.pitch = columns - width; + } + #if SPAN_RUNTIME_SUPPORT /// /// Initializes a new instance of the struct. From 9f406d39961687934f95fb88b141aa5c32605bc7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 02:27:40 +0200 Subject: [PATCH 061/200] Bug fixes, added Memory2D tests --- .../Memory/Memory2D{T}.cs | 14 +- .../Memory/ReadOnlySpan2D{T}.cs | 7 +- .../Memory/Span2D{T}.cs | 7 +- .../Memory/Test_Memory2D{T}.cs | 447 ++++++++++++++++++ ...UnitTests.HighPerformance.Shared.projitems | 1 + 5 files changed, 463 insertions(+), 13 deletions(-) create mode 100644 UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 64f3ca4cd1e..6be6091b9cc 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -487,7 +487,7 @@ public Span2D Span /// /// A new instance representing a slice of the current one. [Pure] - public unsafe Memory2D Slice(int row, int column, int height, int width) + public Memory2D Slice(int row, int column, int height, int width) { if ((uint)row >= Height) { @@ -513,7 +513,7 @@ public unsafe Memory2D Slice(int row, int column, int height, int width) shift = ((this.width + this.pitch) * row) + column, pitch = this.pitch + (this.width - width); - IntPtr offset = (IntPtr)((byte*)this.offset + shift); + IntPtr offset = this.offset + (shift * Unsafe.SizeOf()); if (this.instance is Memory memory) { @@ -573,6 +573,11 @@ public unsafe MemoryHandle Pin() { if (!(this.instance is null)) { + if (this.instance is Memory memory) + { + return memory.Pin(); + } + GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned); void* pointer = Unsafe.AsPointer(ref this.instance.DangerousGetObjectDataReferenceAt(this.offset)); @@ -605,7 +610,10 @@ public bool TryGetMemory(out Memory memory) else if (this.instance.GetType() == typeof(T[])) { // If it's a T[] array, also handle the initial offset - memory = Unsafe.As(this.instance).AsMemory((int)this.offset, this.height * this.width); + T[] array = Unsafe.As(this.instance); + int index = array.AsSpan().IndexOf(ref array.DangerousGetObjectDataReferenceAt(this.offset)); + + memory = Unsafe.As(this.instance).AsMemory(index, this.height * this.width); } else { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 4e61c440a3e..bd44b45367f 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -685,12 +685,9 @@ public ReadOnlySpan2D Slice(int row, int column, int width, int height) return new ReadOnlySpan2D(r0, height, width, pitch); #else - unsafe - { - IntPtr offset = (IntPtr)((byte*)this.offset + shift); + IntPtr offset = this.offset + (shift * Unsafe.SizeOf()); - return new ReadOnlySpan2D(this.instance!, offset, height, width, pitch); - } + return new ReadOnlySpan2D(this.instance!, offset, height, width, pitch); #endif } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 3982f8303d4..65a3a246e51 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -769,12 +769,9 @@ public Span2D Slice(int row, int column, int height, int width) return new Span2D(ref r0, height, width, pitch); #else - unsafe - { - IntPtr offset = (IntPtr)((byte*)this.Offset + shift); + IntPtr offset = this.Offset + (shift * Unsafe.SizeOf()); - return new Span2D(this.Instance!, offset, height, width, pitch); - } + return new Span2D(this.Instance!, offset, height, width, pitch); #endif } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs new file mode 100644 index 00000000000..ce3289ff8fb --- /dev/null +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs @@ -0,0 +1,447 @@ +// 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.CodeAnalysis; +using Microsoft.Toolkit.HighPerformance.Memory; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.HighPerformance.Memory +{ + [TestClass] + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")] + public class Test_Memory2DT + { + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_Empty() + { + Memory2D empty1 = default; + + Assert.IsTrue(empty1.IsEmpty); + Assert.AreEqual(empty1.Size, 0); + Assert.AreEqual(empty1.Width, 0); + Assert.AreEqual(empty1.Height, 0); + + Memory2D empty2 = Memory2D.Empty; + + Assert.IsTrue(empty2.IsEmpty); + Assert.AreEqual(empty2.Size, 0); + Assert.AreEqual(empty2.Width, 0); + Assert.AreEqual(empty2.Height, 0); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_Array1DConstructor() + { + int[] array = + { + 1, 2, 3, 4, 5, 6 + }; + + Memory2D span2d = new Memory2D(array, 1, 2, 2, 1); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d.Span[0, 0], 2); + Assert.AreEqual(span2d.Span[1, 1], 6); + + Assert.ThrowsException(() => new Memory2D(new string[1], 1, 1)); + Assert.ThrowsException(() => new Memory2D(array, -99, 1, 1, 1)); + Assert.ThrowsException(() => new Memory2D(array, 0, -10, 1, 1)); + Assert.ThrowsException(() => new Memory2D(array, 0, 1, 1, -1)); + Assert.ThrowsException(() => new Memory2D(array, 0, 1, -100, 1)); + Assert.ThrowsException(() => new Memory2D(array, 0, 10, 1, 120)); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_Array2DConstructor_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Memory2D span2d = new Memory2D(array); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Width, 3); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d.Span[0, 1], 2); + Assert.AreEqual(span2d.Span[1, 2], 6); + + Assert.ThrowsException(() => new Memory2D(new string[1, 2])); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_Array2DConstructor_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Memory2D span2d = new Memory2D(array, 0, 1, 2, 2); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d.Span[0, 0], 2); + Assert.AreEqual(span2d.Span[1, 1], 6); + + Assert.ThrowsException(() => new Memory2D(new string[1, 2], 0, 0, 2, 2)); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_Array3DConstructor_1() + { + int[,,] array = + { + { + { 1, 2, 3 }, + { 4, 5, 6 } + }, + { + { 10, 20, 30 }, + { 40, 50, 60 } + } + }; + + Memory2D span2d = new Memory2D(array, 1); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Width, 3); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d.Span[0, 1], 20); + Assert.AreEqual(span2d.Span[1, 2], 60); + + Assert.ThrowsException(() => new Memory2D(array, -1)); + Assert.ThrowsException(() => new Memory2D(array, 20)); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_Array3DConstructor_2() + { + int[,,] array = + { + { + { 1, 2, 3 }, + { 4, 5, 6 } + }, + { + { 10, 20, 30 }, + { 40, 50, 60 } + } + }; + + Memory2D span2d = new Memory2D(array, 1, 0, 1, 2, 2); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d.Span[0, 0], 20); + Assert.AreEqual(span2d.Span[1, 1], 60); + + Assert.ThrowsException(() => new Memory2D(array, -1, 1, 1, 1, 1)); + Assert.ThrowsException(() => new Memory2D(array, 1, -1, 1, 1, 1)); + Assert.ThrowsException(() => new Memory2D(array, 1, 1, -1, 1, 1)); + Assert.ThrowsException(() => new Memory2D(array, 1, 1, 1, -1, 1)); + Assert.ThrowsException(() => new Memory2D(array, 1, 1, 1, 1, -1)); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_MemoryConstructor() + { + Memory memory = new[] + { + 1, 2, 3, 4, 5, 6 + }; + + Memory2D span2d = new Memory2D(memory, 1, 2, 2, 1); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d.Span[0, 0], 2); + Assert.AreEqual(span2d.Span[1, 1], 6); + + Assert.ThrowsException(() => new Memory2D(memory, -99, 1, 1, 1)); + Assert.ThrowsException(() => new Memory2D(memory, 0, -10, 1, 1)); + Assert.ThrowsException(() => new Memory2D(memory, 0, 1, 1, -1)); + Assert.ThrowsException(() => new Memory2D(memory, 0, 1, -100, 1)); + Assert.ThrowsException(() => new Memory2D(memory, 0, 10, 1, 120)); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_Slice_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Memory2D span2d = new Memory2D(array); + + Memory2D slice1 = span2d.Slice(1, 1, 1, 2); + + Assert.AreEqual(slice1.Size, 2); + Assert.AreEqual(slice1.Height, 1); + Assert.AreEqual(slice1.Width, 2); + Assert.AreEqual(slice1.Span[0, 0], 5); + Assert.AreEqual(slice1.Span[0, 1], 6); + + Memory2D slice2 = span2d.Slice(0, 1, 2, 2); + + Assert.AreEqual(slice2.Size, 4); + Assert.AreEqual(slice2.Height, 2); + Assert.AreEqual(slice2.Width, 2); + Assert.AreEqual(slice2.Span[0, 0], 2); + Assert.AreEqual(slice2.Span[1, 0], 5); + Assert.AreEqual(slice2.Span[1, 1], 6); + + Assert.ThrowsException(() => new Memory2D(array).Slice(-1, 1, 1, 1)); + Assert.ThrowsException(() => new Memory2D(array).Slice(1, -1, 1, 1)); + Assert.ThrowsException(() => new Memory2D(array).Slice(1, 1, 1, -1)); + Assert.ThrowsException(() => new Memory2D(array).Slice(1, 1, -1, 1)); + Assert.ThrowsException(() => new Memory2D(array).Slice(10, 1, 1, 1)); + Assert.ThrowsException(() => new Memory2D(array).Slice(1, 12, 1, 12)); + Assert.ThrowsException(() => new Memory2D(array).Slice(1, 1, 55, 1)); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_Slice_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Memory2D span2d = new Memory2D(array); + + Memory2D slice1 = span2d.Slice(0, 0, 2, 2); + + Assert.AreEqual(slice1.Size, 4); + Assert.AreEqual(slice1.Height, 2); + Assert.AreEqual(slice1.Width, 2); + Assert.AreEqual(slice1.Span[0, 0], 1); + Assert.AreEqual(slice1.Span[1, 1], 5); + + Memory2D slice2 = slice1.Slice(1, 0, 1, 2); + + Assert.AreEqual(slice2.Size, 2); + Assert.AreEqual(slice2.Height, 1); + Assert.AreEqual(slice2.Width, 2); + Assert.AreEqual(slice2.Span[0, 0], 4); + Assert.AreEqual(slice2.Span[0, 1], 5); + + Memory2D slice3 = slice2.Slice(0, 1, 1, 1); + + Assert.AreEqual(slice3.Size, 1); + Assert.AreEqual(slice3.Height, 1); + Assert.AreEqual(slice3.Width, 1); + Assert.AreEqual(slice3.Span[0, 0], 5); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_TryGetMemory_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Memory2D span2d = new Memory2D(array); + + bool success = span2d.TryGetMemory(out Memory memory); + + Assert.IsFalse(success); + Assert.IsTrue(memory.IsEmpty); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_TryGetMemory_2() + { + int[] array = { 1, 2, 3, 4 }; + + Memory2D span2d = new Memory2D(array, 2, 2); + + bool success = span2d.TryGetMemory(out Memory memory); + + Assert.IsTrue(success); + Assert.AreEqual(memory.Length, array.Length); + Assert.AreEqual(memory.Span[2], 3); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_TryGetMemory_3() + { + Memory data = new[] { 1, 2, 3, 4 }; + + Memory2D span2d = new Memory2D(data, 2, 2); + + bool success = span2d.TryGetMemory(out Memory memory); + + Assert.IsTrue(success); + Assert.AreEqual(memory.Length, data.Length); + Assert.AreEqual(memory.Span[2], 3); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public unsafe void Test_Memory2DT_Pin_1() + { + int[] array = { 1, 2, 3, 4 }; + + Memory2D span2d = new Memory2D(array, 2, 2); + + using var pin = span2d.Pin(); + + Assert.AreEqual(((int*)pin.Pointer)[0], 1); + Assert.AreEqual(((int*)pin.Pointer)[3], 4); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public unsafe void Test_Memory2DT_Pin_2() + { + int[] array = { 1, 2, 3, 4 }; + + Memory2D span2d = new Memory2D(array, 2, 2); + + using var pin = span2d.Pin(); + + Assert.AreEqual(((int*)pin.Pointer)[0], 1); + Assert.AreEqual(((int*)pin.Pointer)[3], 4); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_ToArray_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Memory2D span2d = new Memory2D(array); + + int[,] copy = span2d.ToArray(); + + Assert.AreEqual(copy.GetLength(0), array.GetLength(0)); + Assert.AreEqual(copy.GetLength(1), array.GetLength(1)); + + CollectionAssert.AreEqual(array, copy); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_ToArray_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Memory2D span2d = new Memory2D(array, 0, 0, 2, 2); + + int[,] copy = span2d.ToArray(); + + Assert.AreEqual(copy.GetLength(0), 2); + Assert.AreEqual(copy.GetLength(1), 2); + + int[,] expected = + { + { 1, 2 }, + { 4, 5 } + }; + + CollectionAssert.AreEqual(expected, copy); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_Equals() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Memory2D span2d = new Memory2D(array); + + Assert.IsFalse(span2d.Equals(null)); + Assert.IsFalse(span2d.Equals(new Memory2D(array, 0, 1, 2, 2))); + Assert.IsTrue(span2d.Equals(new Memory2D(array))); + Assert.IsTrue(span2d.Equals(span2d)); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_GetHashCode() + { + Assert.AreEqual(Memory2D.Empty.GetHashCode(), 0); + + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Memory2D span2d = new Memory2D(array); + + int a = span2d.GetHashCode(), b = span2d.GetHashCode(); + + Assert.AreEqual(a, b); + + int c = new Memory2D(array, 0, 1, 2, 2).GetHashCode(); + + Assert.AreNotEqual(a, c); + } + + [TestCategory("Memory2DT")] + [TestMethod] + public void Test_Memory2DT_ToString() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Memory2D span2d = new Memory2D(array); + + string text = span2d.ToString(); + + const string expected = "Microsoft.Toolkit.HighPerformance.Memory.Memory2D[2, 3]"; + + Assert.AreEqual(text, expected); + } + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems index 3d9500684f7..9d5ea59849d 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems +++ b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems @@ -40,6 +40,7 @@ + From be73d4ee0ce825bab44296c61e552cfacc735981 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 02:33:56 +0200 Subject: [PATCH 062/200] Updated ReadOnlyMemory2D --- .../Memory/Memory2D{T}.cs | 4 +- .../Memory/ReadOnlyMemory2D{T}.cs | 308 ++++++++++++------ 2 files changed, 205 insertions(+), 107 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 6be6091b9cc..c246ad4ba89 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -544,7 +544,7 @@ public Memory2D Slice(int row, int column, int height, int width) /// /// Copies the contents of this into a destination instance. - /// For this API to succeed, the target has to have the same shape as the current one. + /// For this API to succeed, the target has to have the same shape as the current one. /// /// The destination instance. /// @@ -554,7 +554,7 @@ public Memory2D Slice(int row, int column, int height, int width) /// /// Attempts to copy the current instance to a destination . - /// For this API to succeed, the target has to have the same shape as the current one. + /// For this API to succeed, the target has to have the same shape as the current one. /// /// The target of the copy operation. /// Whether or not the operaation was successful. diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 47f2039097c..c3439e27d17 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -16,7 +16,7 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// /// A readonly version of . /// - /// The type of items in the current instance. + /// The type of items in the current instance. public readonly struct ReadOnlyMemory2D : IEquatable> { /// @@ -48,18 +48,18 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// Initializes a new instance of the struct. /// /// The target array to wrap. - /// The initial offset within . - /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. /// /// Thrown when doesn't match . /// - /// - /// Thrown when either , or are invalid. + /// + /// Thrown when either or are invalid. /// + /// The total area must match the lenght of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory2D(T[] array, int offset, int width, int height) - : this(array, offset, width, height, 0) + public ReadOnlyMemory2D(T[] array, int height, int width) + : this(array, 0, height, width, 0) { } @@ -68,32 +68,58 @@ public ReadOnlyMemory2D(T[] array, int offset, int width, int height) /// /// The target array to wrap. /// The initial offset within . - /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. /// The pitch in the resulting 2D area. /// /// Thrown when doesn't match . /// /// - /// Thrown when either , , - /// or are invalid. + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory2D(T[] array, int offset, int width, int height, int pitch) + public ReadOnlyMemory2D(T[] array, int offset, int height, int width, int pitch) { if (array.IsCovariant()) { ThrowHelper.ThrowArrayTypeMismatchException(); } - if ((uint)offset >= (uint)array.Length) + if ((uint)offset > (uint)array.Length) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); } - int remaining = array.Length - offset; + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + if (width == 0 || height == 0) + { + this = default; + + return; + } + + int + remaining = array.Length - offset, + area = ((width + pitch) * (height - 1)) + width; - if ((((uint)width + (uint)pitch) * (uint)height) > (uint)remaining) + if (area > remaining) { ThrowHelper.ThrowArgumentException(); } @@ -106,13 +132,12 @@ public ReadOnlyMemory2D(T[] array, int offset, int width, int height, int pitch) } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct wrapping a 2D array. /// - /// The target array to wrap. + /// The given 2D array to wrap. /// /// Thrown when doesn't match . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlyMemory2D(T[,]? array) { if (array is null) @@ -128,7 +153,7 @@ public ReadOnlyMemory2D(T[,]? array) } this.instance = array; - this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReference()); + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(0, 0)); this.height = array.GetLength(0); this.width = array.GetLength(1); this.pitch = 0; @@ -140,8 +165,8 @@ public ReadOnlyMemory2D(T[,]? array) /// The given 2D array to wrap. /// The target row to map within . /// The target column to map within . - /// The width to map within . /// The height to map within . + /// The width to map within . /// /// Thrown when doesn't match . /// @@ -149,21 +174,8 @@ public ReadOnlyMemory2D(T[,]? array) /// Thrown when either , or /// are negative or not within the bounds that are valid for . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory2D(T[,]? array, int row, int column, int width, int height) + public ReadOnlyMemory2D(T[,] array, int row, int column, int height, int width) { - if (array is null) - { - if ((row | column | width | height) != 0) - { - ThrowHelper.ThrowArgumentException(); - } - - this = default; - - return; - } - if (array.IsCovariant()) { ThrowHelper.ThrowArrayTypeMismatchException(); @@ -183,21 +195,21 @@ public ReadOnlyMemory2D(T[,]? array, int row, int column, int width, int height) ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } - if (width > (columns - column)) + if ((uint)height > (uint)(rows - row)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - if (height > (rows - row)) + if ((uint)width > (uint)(columns - column)) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } this.instance = array; this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column)); - this.height = height; + this.height = array.GetLength(0); this.width = width; - this.pitch = row + (array.GetLength(1) - column); + this.pitch = columns - width; } /// @@ -208,7 +220,7 @@ public ReadOnlyMemory2D(T[,]? array, int row, int column, int width, int height) /// /// Thrown when doesn't match . /// - /// Thrown when either is invalid. + /// Thrown when a parameter is invalid. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlyMemory2D(T[,,] array, int depth) { @@ -229,54 +241,77 @@ public ReadOnlyMemory2D(T[,,] array, int depth) this.pitch = 0; } -#if SPAN_RUNTIME_SUPPORT /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct wrapping a layer in a 3D array. /// - /// The target to wrap. - /// The initial offset within . - /// The width of each row in the resulting 2D area. - /// The height of the resulting 2D area. - /// - /// Thrown when either , or are invalid. + /// The given 3D array to wrap. + /// The target layer to map within . + /// The target row to map within . + /// The target column to map within . + /// The height to map within . + /// The width to map within . + /// + /// Thrown when doesn't match . /// + /// Thrown when a parameter is invalid. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory2D(Memory memory, int offset, int width, int height) - : this((ReadOnlyMemory)memory, offset, width, height, 0) + public ReadOnlyMemory2D(T[,,] array, int depth, int row, int column, int height, int width) { - } + if (array.IsCovariant()) + { + ThrowHelper.ThrowArrayTypeMismatchException(); + } - /// - /// Initializes a new instance of the struct. - /// - /// The target to wrap. - /// The initial offset within . - /// The width of each row in the resulting 2D area. - /// The height of the resulting 2D area. - /// The pitch in the resulting 2D area. - /// - /// Thrown when either , , - /// or are invalid. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory2D(Memory memory, int offset, int width, int height, int pitch) - : this((ReadOnlyMemory)memory, offset, width, height, pitch) - { + if ((uint)depth >= (uint)array.GetLength(0)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); + } + + int + rows = array.GetLength(1), + columns = array.GetLength(2); + + if ((uint)row >= (uint)rows) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); + } + + if ((uint)column >= (uint)columns) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); + } + + if ((uint)height > (uint)(rows - row)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if ((uint)width > (uint)(columns - column)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, row, column)); + this.height = height; + this.width = width; + this.pitch = columns - width; } +#if SPAN_RUNTIME_SUPPORT /// /// Initializes a new instance of the struct. /// /// The target to wrap. - /// The initial offset within . - /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. - /// - /// Thrown when either , or are invalid. + /// The width of each row in the resulting 2D area. + /// + /// Thrown when either or are invalid. /// + /// The total area must match the lenght of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int width, int height) - : this(memory, offset, width, height, 0) + public ReadOnlyMemory2D(ReadOnlyMemory memory, int height, int width) + : this(memory, 0, height, width, 0) { } @@ -285,24 +320,50 @@ public ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int width, int hei /// /// The target to wrap. /// The initial offset within . - /// The width of each row in the resulting 2D area. /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. /// The pitch in the resulting 2D area. /// - /// Thrown when either , , - /// or are invalid. + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int width, int height, int pitch) + public ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int height, int width, int pitch) { - if ((uint)offset >= (uint)memory.Length) + if ((uint)offset > (uint)memory.Length) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); } - int remaining = memory.Length - offset; + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } - if ((((uint)width + (uint)pitch) * (uint)height) > (uint)remaining) + if (width == 0 || height == 0) + { + this = default; + + return; + } + + int + remaining = memory.Length - offset, + area = ((width + pitch) * (height - 1)) + width; + + if (area > remaining) { ThrowHelper.ThrowArgumentException(); } @@ -348,12 +409,30 @@ public bool IsEmpty } /// - /// Gets the length of the current instance. + /// Gets the length of the current instance. + /// + public int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Height * Width; + } + + /// + /// Gets the height of the underlying 2D memory area. + /// + public int Height + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.height; + } + + /// + /// Gets the width of the underlying 2D memory area. /// - public int Length + public int Width { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.height * this.width; + get => this.width; } /// @@ -397,17 +476,17 @@ public ReadOnlySpan2D Span /// /// The target row to map within the current instance. /// The target column to map within the current instance. - /// The width to map within the current instance. /// The height to map within the current instance. - /// + /// The width to map within the current instance. + /// /// Thrown when either , or /// are negative or not within the bounds that are valid for the current instance. /// - /// A new instance representing a slice of the current one. + /// A new instance representing a slice of the current one. [Pure] - public unsafe ReadOnlyMemory2D Slice(int row, int column, int width, int height) + public ReadOnlyMemory2D Slice(int row, int column, int height, int width) { - if ((uint)row >= this.height) + if ((uint)row >= Height) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); } @@ -417,51 +496,62 @@ public unsafe ReadOnlyMemory2D Slice(int row, int column, int width, int heig ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } + if ((uint)height > (Height - row)) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + if ((uint)width > (this.width - column)) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } - if ((uint)height > (this.height - row)) + int + shift = ((this.width + this.pitch) * row) + column, + pitch = this.pitch + (this.width - width); + + IntPtr offset = this.offset + (shift * Unsafe.SizeOf()); + + if (this.instance is Memory memory) { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); - } + // Memory instance are always already sliced + object instance = memory.Slice((int)offset); - int shift = ((this.width + this.pitch) * row) + column; - IntPtr offset = (IntPtr)((byte*)this.offset + shift); + return new ReadOnlyMemory2D(instance, default, height, width, pitch); + } - return new ReadOnlyMemory2D(this.instance!, offset, height, width, this.pitch); + return new ReadOnlyMemory2D(this.instance!, offset, height, width, pitch); } /// - /// Copies the contents of this into a destination instance. + /// Copies the contents of this into a destination instance. /// /// The destination instance. /// - /// Thrown when is shorter than the source instance. + /// Thrown when is shorter than the source instance. /// public void CopyTo(Memory destination) => Span.CopyTo(destination.Span); /// - /// Attempts to copy the current instance to a destination . + /// Attempts to copy the current instance to a destination . /// /// The target of the copy operation. /// Whether or not the operaation was successful. public bool TryCopyTo(Memory destination) => Span.TryCopyTo(destination.Span); /// - /// Copies the contents of this into a destination instance. - /// For this API to succeed, the target has to have the same shape as the current one. + /// Copies the contents of this into a destination instance. + /// For this API to succeed, the target has to have the same shape as the current one. /// /// The destination instance. /// - /// Thrown when is shorter than the source instance. + /// Thrown when is shorter than the source instance. /// public void CopyTo(Memory2D destination) => Span.CopyTo(destination.Span); /// - /// Attempts to copy the current instance to a destination . - /// For this API to succeed, the target has to have the same shape as the current one. + /// Attempts to copy the current instance to a destination . + /// For this API to succeed, the target has to have the same shape as the current one. /// /// The target of the copy operation. /// Whether or not the operaation was successful. @@ -480,6 +570,11 @@ public unsafe MemoryHandle Pin() { if (!(this.instance is null)) { + if (this.instance is Memory memory) + { + return memory.Pin(); + } + GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned); void* pointer = Unsafe.AsPointer(ref this.instance.DangerousGetObjectDataReferenceAt(this.offset)); @@ -493,7 +588,7 @@ public unsafe MemoryHandle Pin() /// /// Tries to get a instance, if the underlying buffer is contiguous. /// - /// The resulting , in case of success. + /// The resulting , in case of success. /// Whether or not was correctly assigned. public bool TryGetMemory(out ReadOnlyMemory memory) { @@ -506,13 +601,16 @@ public bool TryGetMemory(out ReadOnlyMemory memory) } else if (this.instance.GetType() == typeof(ReadOnlyMemory)) { - // If the object is a ReadOnlyMemory, just slice it as needed + // If the object is a Memory, just slice it as needed memory = ((ReadOnlyMemory)this.instance).Slice(0, this.height * this.width); } else if (this.instance.GetType() == typeof(T[])) { // If it's a T[] array, also handle the initial offset - memory = new ReadOnlyMemory(Unsafe.As(this.instance), (int)this.offset, this.height * this.width); + T[] array = Unsafe.As(this.instance); + int index = array.AsSpan().IndexOf(ref array.DangerousGetObjectDataReferenceAt(this.offset)); + + memory = Unsafe.As(this.instance).AsMemory(index, this.height * this.width); } else { From 195593324054cb1a4cd42e0c42adeb1ce5ec6d82 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 02:38:33 +0200 Subject: [PATCH 063/200] Added ReadOnlySpan2D string constructors --- .../Memory/Memory2D{T}.cs | 2 +- .../Memory/ReadOnlyMemory2D{T}.cs | 86 ++++++++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index c246ad4ba89..39e8933b040 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -613,7 +613,7 @@ public bool TryGetMemory(out Memory memory) T[] array = Unsafe.As(this.instance); int index = array.AsSpan().IndexOf(ref array.DangerousGetObjectDataReferenceAt(this.offset)); - memory = Unsafe.As(this.instance).AsMemory(index, this.height * this.width); + memory = array.AsMemory(index, this.height * this.width); } else { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index c3439e27d17..27c479381e9 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -44,6 +44,82 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// private readonly int pitch; + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. + /// + /// Thrown when either or are invalid. + /// + /// The total area must match the lenght of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory2D(string text, int height, int width) + : this(text, 0, height, width, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// 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. + /// + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory2D(string text, int offset, int height, int width, int pitch) + { + if ((uint)offset > (uint)text.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + if (width == 0 || height == 0) + { + this = default; + + return; + } + + int + remaining = text.Length - offset, + area = ((width + pitch) * (height - 1)) + width; + + if (area > remaining) + { + ThrowHelper.ThrowArgumentException(); + } + + this.instance = text; + this.offset = text.DangerousGetObjectDataByteOffset(ref text.DangerousGetReferenceAt(offset)); + this.height = height; + this.width = width; + this.pitch = pitch; + } + /// /// Initializes a new instance of the struct. /// @@ -599,6 +675,14 @@ public bool TryGetMemory(out ReadOnlyMemory memory) { memory = default; } + else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string)) + { + string text = Unsafe.As(this.instance); + int index = text.AsSpan().IndexOf(text.DangerousGetObjectDataReferenceAt(this.offset)); + ReadOnlyMemory temp = text.AsMemory(index, Size); + + memory = Unsafe.As, ReadOnlyMemory>(ref temp); + } else if (this.instance.GetType() == typeof(ReadOnlyMemory)) { // If the object is a Memory, just slice it as needed @@ -610,7 +694,7 @@ public bool TryGetMemory(out ReadOnlyMemory memory) T[] array = Unsafe.As(this.instance); int index = array.AsSpan().IndexOf(ref array.DangerousGetObjectDataReferenceAt(this.offset)); - memory = Unsafe.As(this.instance).AsMemory(index, this.height * this.width); + memory = array.AsMemory(index, this.height * this.width); } else { From 1aebcb249482c0f796495112cfc0d711dd3d1a87 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 02:46:38 +0200 Subject: [PATCH 064/200] Reverted new APIs, added ReadOnlyMemory2D tests --- .../Memory/ReadOnlyMemory2D{T}.cs | 76 --- .../Memory/Test_ReadOnlyMemory2D{T}.cs | 447 ++++++++++++++++++ ...UnitTests.HighPerformance.Shared.projitems | 1 + 3 files changed, 448 insertions(+), 76 deletions(-) create mode 100644 UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 27c479381e9..1c2deb842b2 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -44,82 +44,6 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// private readonly int pitch; - /// - /// Initializes a new instance of the struct. - /// - /// The target to wrap. - /// The height of the resulting 2D area. - /// The width of each row in the resulting 2D area. - /// - /// Thrown when either or are invalid. - /// - /// The total area must match the lenght of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory2D(string text, int height, int width) - : this(text, 0, height, width, 0) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The target to wrap. - /// 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. - /// - /// Thrown when one of the input parameters is out of range. - /// - /// - /// Thrown when the requested area is outside of bounds for . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory2D(string text, int offset, int height, int width, int pitch) - { - if ((uint)offset > (uint)text.Length) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); - } - - if (height < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); - } - - if (width < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); - } - - if (pitch < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); - } - - if (width == 0 || height == 0) - { - this = default; - - return; - } - - int - remaining = text.Length - offset, - area = ((width + pitch) * (height - 1)) + width; - - if (area > remaining) - { - ThrowHelper.ThrowArgumentException(); - } - - this.instance = text; - this.offset = text.DangerousGetObjectDataByteOffset(ref text.DangerousGetReferenceAt(offset)); - this.height = height; - this.width = width; - this.pitch = pitch; - } - /// /// Initializes a new instance of the struct. /// diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs new file mode 100644 index 00000000000..32d64a1848b --- /dev/null +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs @@ -0,0 +1,447 @@ +// 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.CodeAnalysis; +using Microsoft.Toolkit.HighPerformance.Memory; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.HighPerformance.Memory +{ + [TestClass] + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")] + public class Test_ReadOnlyMemory2DT + { + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_Empty() + { + ReadOnlyMemory2D empty1 = default; + + Assert.IsTrue(empty1.IsEmpty); + Assert.AreEqual(empty1.Size, 0); + Assert.AreEqual(empty1.Width, 0); + Assert.AreEqual(empty1.Height, 0); + + ReadOnlyMemory2D empty2 = ReadOnlyMemory2D.Empty; + + Assert.IsTrue(empty2.IsEmpty); + Assert.AreEqual(empty2.Size, 0); + Assert.AreEqual(empty2.Width, 0); + Assert.AreEqual(empty2.Height, 0); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_Array1DConstructor() + { + int[] array = + { + 1, 2, 3, 4, 5, 6 + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 1, 2, 2, 1); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d.Span[0, 0], 2); + Assert.AreEqual(span2d.Span[1, 1], 6); + + Assert.ThrowsException(() => new ReadOnlyMemory2D(new string[1], 1, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, -99, 1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, -10, 1, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, 1, 1, -1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, 1, -100, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, 10, 1, 120)); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_Array2DConstructor_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Width, 3); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d.Span[0, 1], 2); + Assert.AreEqual(span2d.Span[1, 2], 6); + + Assert.ThrowsException(() => new ReadOnlyMemory2D(new string[1, 2])); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_Array2DConstructor_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 0, 1, 2, 2); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d.Span[0, 0], 2); + Assert.AreEqual(span2d.Span[1, 1], 6); + + Assert.ThrowsException(() => new ReadOnlyMemory2D(new string[1, 2], 0, 0, 2, 2)); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_Array3DConstructor_1() + { + int[,,] array = + { + { + { 1, 2, 3 }, + { 4, 5, 6 } + }, + { + { 10, 20, 30 }, + { 40, 50, 60 } + } + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 1); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Width, 3); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d.Span[0, 1], 20); + Assert.AreEqual(span2d.Span[1, 2], 60); + + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, -1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 20)); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_Array3DConstructor_2() + { + int[,,] array = + { + { + { 1, 2, 3 }, + { 4, 5, 6 } + }, + { + { 10, 20, 30 }, + { 40, 50, 60 } + } + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 1, 0, 1, 2, 2); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d.Span[0, 0], 20); + Assert.AreEqual(span2d.Span[1, 1], 60); + + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, -1, 1, 1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 1, -1, 1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 1, 1, -1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 1, 1, 1, -1, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 1, 1, 1, 1, -1)); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_ReadOnlyMemoryConstructor() + { + ReadOnlyMemory memory = new[] + { + 1, 2, 3, 4, 5, 6 + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(memory, 1, 2, 2, 1); + + Assert.IsFalse(span2d.IsEmpty); + Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Width, 2); + Assert.AreEqual(span2d.Height, 2); + Assert.AreEqual(span2d.Span[0, 0], 2); + Assert.AreEqual(span2d.Span[1, 1], 6); + + Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, -99, 1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, 0, -10, 1, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, 0, 1, 1, -1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, 0, 1, -100, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, 0, 10, 1, 120)); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_Slice_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + + ReadOnlyMemory2D slice1 = span2d.Slice(1, 1, 1, 2); + + Assert.AreEqual(slice1.Size, 2); + Assert.AreEqual(slice1.Height, 1); + Assert.AreEqual(slice1.Width, 2); + Assert.AreEqual(slice1.Span[0, 0], 5); + Assert.AreEqual(slice1.Span[0, 1], 6); + + ReadOnlyMemory2D slice2 = span2d.Slice(0, 1, 2, 2); + + Assert.AreEqual(slice2.Size, 4); + Assert.AreEqual(slice2.Height, 2); + Assert.AreEqual(slice2.Width, 2); + Assert.AreEqual(slice2.Span[0, 0], 2); + Assert.AreEqual(slice2.Span[1, 0], 5); + Assert.AreEqual(slice2.Span[1, 1], 6); + + Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(-1, 1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(1, -1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(1, 1, 1, -1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(1, 1, -1, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(10, 1, 1, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(1, 12, 1, 12)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(1, 1, 55, 1)); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_Slice_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + + ReadOnlyMemory2D slice1 = span2d.Slice(0, 0, 2, 2); + + Assert.AreEqual(slice1.Size, 4); + Assert.AreEqual(slice1.Height, 2); + Assert.AreEqual(slice1.Width, 2); + Assert.AreEqual(slice1.Span[0, 0], 1); + Assert.AreEqual(slice1.Span[1, 1], 5); + + ReadOnlyMemory2D slice2 = slice1.Slice(1, 0, 1, 2); + + Assert.AreEqual(slice2.Size, 2); + Assert.AreEqual(slice2.Height, 1); + Assert.AreEqual(slice2.Width, 2); + Assert.AreEqual(slice2.Span[0, 0], 4); + Assert.AreEqual(slice2.Span[0, 1], 5); + + ReadOnlyMemory2D slice3 = slice2.Slice(0, 1, 1, 1); + + Assert.AreEqual(slice3.Size, 1); + Assert.AreEqual(slice3.Height, 1); + Assert.AreEqual(slice3.Width, 1); + Assert.AreEqual(slice3.Span[0, 0], 5); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + + bool success = span2d.TryGetMemory(out ReadOnlyMemory memory); + + Assert.IsFalse(success); + Assert.IsTrue(memory.IsEmpty); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_2() + { + int[] array = { 1, 2, 3, 4 }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 2, 2); + + bool success = span2d.TryGetMemory(out ReadOnlyMemory memory); + + Assert.IsTrue(success); + Assert.AreEqual(memory.Length, array.Length); + Assert.AreEqual(memory.Span[2], 3); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_3() + { + ReadOnlyMemory data = new[] { 1, 2, 3, 4 }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(data, 2, 2); + + bool success = span2d.TryGetMemory(out ReadOnlyMemory memory); + + Assert.IsTrue(success); + Assert.AreEqual(memory.Length, data.Length); + Assert.AreEqual(memory.Span[2], 3); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public unsafe void Test_ReadOnlyMemory2DT_Pin_1() + { + int[] array = { 1, 2, 3, 4 }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 2, 2); + + using var pin = span2d.Pin(); + + Assert.AreEqual(((int*)pin.Pointer)[0], 1); + Assert.AreEqual(((int*)pin.Pointer)[3], 4); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public unsafe void Test_ReadOnlyMemory2DT_Pin_2() + { + int[] array = { 1, 2, 3, 4 }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 2, 2); + + using var pin = span2d.Pin(); + + Assert.AreEqual(((int*)pin.Pointer)[0], 1); + Assert.AreEqual(((int*)pin.Pointer)[3], 4); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_ToArray_1() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + + int[,] copy = span2d.ToArray(); + + Assert.AreEqual(copy.GetLength(0), array.GetLength(0)); + Assert.AreEqual(copy.GetLength(1), array.GetLength(1)); + + CollectionAssert.AreEqual(array, copy); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_ToArray_2() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 0, 0, 2, 2); + + int[,] copy = span2d.ToArray(); + + Assert.AreEqual(copy.GetLength(0), 2); + Assert.AreEqual(copy.GetLength(1), 2); + + int[,] expected = + { + { 1, 2 }, + { 4, 5 } + }; + + CollectionAssert.AreEqual(expected, copy); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_Equals() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + + Assert.IsFalse(span2d.Equals(null)); + Assert.IsFalse(span2d.Equals(new ReadOnlyMemory2D(array, 0, 1, 2, 2))); + Assert.IsTrue(span2d.Equals(new ReadOnlyMemory2D(array))); + Assert.IsTrue(span2d.Equals(span2d)); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_GetHashCode() + { + Assert.AreEqual(ReadOnlyMemory2D.Empty.GetHashCode(), 0); + + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + + int a = span2d.GetHashCode(), b = span2d.GetHashCode(); + + Assert.AreEqual(a, b); + + int c = new ReadOnlyMemory2D(array, 0, 1, 2, 2).GetHashCode(); + + Assert.AreNotEqual(a, c); + } + + [TestCategory("ReadOnlyMemory2DT")] + [TestMethod] + public void Test_ReadOnlyMemory2DT_ToString() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + + string text = span2d.ToString(); + + const string expected = "Microsoft.Toolkit.HighPerformance.Memory.ReadOnlyMemory2D[2, 3]"; + + Assert.AreEqual(text, expected); + } + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems index 9d5ea59849d..31ef4c2d868 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems +++ b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems @@ -39,6 +39,7 @@ + From c23dff9bbc343585ffc48e0a8969db229c1fb259 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 02:55:55 +0200 Subject: [PATCH 065/200] Fixed tests/types on UWP --- .../Memory/ReadOnlySpan2D{T}.cs | 8 ++++++-- Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs | 8 ++++++-- .../Memory/Test_Memory2D{T}.cs | 4 ++++ .../Memory/Test_ReadOnlyMemory2D{T}.cs | 4 ++++ .../Memory/Test_ReadOnlySpan2D{T}.cs | 6 ++++++ .../Memory/Test_Span2D{T}.cs | 6 ++++++ 6 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index bd44b45367f..e4a0ff7585c 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -620,7 +620,11 @@ public unsafe ref T GetPinnableReference() if (Size != 0) { - r0 = ref this.DangerousGetReference(); +#if SPAN_RUNTIME_SUPPORT + r0 = ref MemoryMarshal.GetReference(this.span); +#else + r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); +#endif } return ref r0; @@ -637,7 +641,7 @@ public ref T DangerousGetReference() #if SPAN_RUNTIME_SUPPORT return ref MemoryMarshal.GetReference(this.span); #else - return ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + return ref this.GetPinnableReference(); #endif } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 65a3a246e51..9a569b436f8 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -704,7 +704,11 @@ public unsafe ref T GetPinnableReference() if (Size != 0) { - r0 = ref this.DangerousGetReference(); +#if SPAN_RUNTIME_SUPPORT + r0 = ref MemoryMarshal.GetReference(this.span); +#else + r0 = ref this.Instance!.DangerousGetObjectDataReferenceAt(this.Offset); +#endif } return ref r0; @@ -721,7 +725,7 @@ public ref T DangerousGetReference() #if SPAN_RUNTIME_SUPPORT return ref MemoryMarshal.GetReference(this.span); #else - return ref this.Instance!.DangerousGetObjectDataReferenceAt(this.Offset); + return ref this.GetPinnableReference(); #endif } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs index ce3289ff8fb..d14190a0a65 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs @@ -163,6 +163,7 @@ public void Test_Memory2DT_Array3DConstructor_2() Assert.ThrowsException(() => new Memory2D(array, 1, 1, 1, 1, -1)); } +#if !WINDOWS_UWP [TestCategory("Memory2DT")] [TestMethod] public void Test_Memory2DT_MemoryConstructor() @@ -187,6 +188,7 @@ public void Test_Memory2DT_MemoryConstructor() Assert.ThrowsException(() => new Memory2D(memory, 0, 1, -100, 1)); Assert.ThrowsException(() => new Memory2D(memory, 0, 10, 1, 120)); } +#endif [TestCategory("Memory2DT")] [TestMethod] @@ -295,6 +297,7 @@ public void Test_Memory2DT_TryGetMemory_2() Assert.AreEqual(memory.Span[2], 3); } +#if !WINDOWS_UWP [TestCategory("Memory2DT")] [TestMethod] public void Test_Memory2DT_TryGetMemory_3() @@ -309,6 +312,7 @@ public void Test_Memory2DT_TryGetMemory_3() Assert.AreEqual(memory.Length, data.Length); Assert.AreEqual(memory.Span[2], 3); } +#endif [TestCategory("Memory2DT")] [TestMethod] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs index 32d64a1848b..886b64d25b4 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs @@ -163,6 +163,7 @@ public void Test_ReadOnlyMemory2DT_Array3DConstructor_2() Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 1, 1, 1, 1, -1)); } +#if !WINDOWS_UWP [TestCategory("ReadOnlyMemory2DT")] [TestMethod] public void Test_ReadOnlyMemory2DT_ReadOnlyMemoryConstructor() @@ -187,6 +188,7 @@ public void Test_ReadOnlyMemory2DT_ReadOnlyMemoryConstructor() Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, 0, 1, -100, 1)); Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, 0, 10, 1, 120)); } +#endif [TestCategory("ReadOnlyMemory2DT")] [TestMethod] @@ -295,6 +297,7 @@ public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_2() Assert.AreEqual(memory.Span[2], 3); } +#if !WINDOWS_UWP [TestCategory("ReadOnlyMemory2DT")] [TestMethod] public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_3() @@ -309,6 +312,7 @@ public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_3() Assert.AreEqual(memory.Length, data.Length); Assert.AreEqual(memory.Span[2], 3); } +#endif [TestCategory("ReadOnlyMemory2DT")] [TestMethod] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs index 0e1b4c2dcea..66787db9050 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs @@ -500,8 +500,14 @@ public void Test_ReadOnlySpan2DT_TryGetReadOnlySpan_1() bool success = span2d.TryGetSpan(out ReadOnlySpan span); +#if WINDOWS_UWP + // Can't get a ReadOnlySpan over a T[,] array on UWP + Assert.IsFalse(success); + Assert.AreEqual(span.Length, 0); +#else Assert.IsTrue(success); Assert.AreEqual(span.Length, span2d.Size); +#endif } [TestCategory("ReadOnlySpan2DT")] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs index 35ef0a515f2..ee5cf93e5d8 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -583,8 +583,14 @@ public void Test_Span2DT_TryGetSpan_1() bool success = span2d.TryGetSpan(out Span span); +#if WINDOWS_UWP + // Can't get a Span over a T[,] array on UWP + Assert.IsFalse(success); + Assert.AreEqual(span.Length, 0); +#else Assert.IsTrue(success); Assert.AreEqual(span.Length, span2d.Size); +#endif } [TestCategory("Span2DT")] From d69d43a2623027cd3a83dce715c7a1307e3c5736 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 12:22:01 +0200 Subject: [PATCH 066/200] Added [ReadOnly]Memory2D.DangerousCreate --- .../Memory/Internals/ThrowHelper.cs | 8 +++ .../Memory/Memory2D{T}.cs | 51 ++++++++++++++++--- .../Memory/ReadOnlyMemory2D{T}.cs | 51 ++++++++++++++++--- 3 files changed, 96 insertions(+), 14 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs b/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs index 123f131ab64..70b5f20142b 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs @@ -37,6 +37,14 @@ public static void ThrowArrayTypeMismatchException() throw new ArrayTypeMismatchException("The given array doesn't match the specified type "); } + /// + /// Throws an when using an array of an invalid type. + /// + public static void ThrowArgumentExceptionForUnsupportedType() + { + throw new ArgumentException("The specified object type is not supported"); + } + /// /// Throws an when the a given coordinate is invalid. /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 39e8933b040..59758764f2b 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -60,7 +60,6 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// Thrown when either or are invalid. /// /// The total area must match the lenght of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(T[] array, int height, int width) : this(array, 0, height, width, 0) { @@ -83,7 +82,6 @@ public Memory2D(T[] array, int height, int width) /// /// Thrown when the requested area is outside of bounds for . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(T[] array, int offset, int height, int width, int pitch) { if (array.IsCovariant()) @@ -224,7 +222,6 @@ public Memory2D(T[,] array, int row, int column, int height, int width) /// Thrown when doesn't match . /// /// Thrown when a parameter is invalid. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(T[,,] array, int depth) { if (array.IsCovariant()) @@ -257,7 +254,6 @@ public Memory2D(T[,,] array, int depth) /// Thrown when doesn't match . /// /// Thrown when a parameter is invalid. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(T[,,] array, int depth, int row, int column, int height, int width) { if (array.IsCovariant()) @@ -312,7 +308,6 @@ public Memory2D(T[,,] array, int depth, int row, int column, int height, int wid /// Thrown when either or are invalid. /// /// The total area must match the lenght of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(Memory memory, int height, int width) : this(memory, 0, height, width, 0) { @@ -332,7 +327,6 @@ public Memory2D(Memory memory, int height, int width) /// /// Thrown when the requested area is outside of bounds for . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory2D(Memory memory, int offset, int height, int width, int pitch) { if ((uint)offset > (uint)memory.Length) @@ -397,6 +391,50 @@ private Memory2D(object instance, IntPtr offset, int height, int width, int pitc this.pitch = pitch; } + /// + /// Creates a new instance from an arbitrary object. + /// + /// The instance holding the data to map. + /// The target reference to point to (it must be within ). + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map. + /// A instaance with the specified parameters. + /// The parameter is not validated, and it's responsability of the caller to ensure it's valid. + /// + /// Thrown when is of an unsupported type. + /// + /// + /// Thrown when one of the input parameters is out of range. + /// + [Pure] + public static Memory2D DangerousCreate(object instance, ref T value, int height, int width, int pitch) + { + if (instance.GetType() == typeof(Memory)) + { + ThrowHelper.ThrowArgumentExceptionForUnsupportedType(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + IntPtr offset = instance.DangerousGetObjectDataByteOffset(ref value); + + return new Memory2D(instance, offset, height, width, pitch); + } + /// /// Gets an empty instance. /// @@ -460,7 +498,6 @@ public Span2D Span } else { - // The only other possible cases is with the instance being an array ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset); return new Span2D(ref r0, this.height, this.width, this.pitch); diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 1c2deb842b2..cd3dcb0e787 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -57,7 +57,6 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// Thrown when either or are invalid. /// /// The total area must match the lenght of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlyMemory2D(T[] array, int height, int width) : this(array, 0, height, width, 0) { @@ -80,7 +79,6 @@ public ReadOnlyMemory2D(T[] array, int height, int width) /// /// Thrown when the requested area is outside of bounds for . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlyMemory2D(T[] array, int offset, int height, int width, int pitch) { if (array.IsCovariant()) @@ -221,7 +219,6 @@ public ReadOnlyMemory2D(T[,] array, int row, int column, int height, int width) /// Thrown when doesn't match . /// /// Thrown when a parameter is invalid. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlyMemory2D(T[,,] array, int depth) { if (array.IsCovariant()) @@ -254,7 +251,6 @@ public ReadOnlyMemory2D(T[,,] array, int depth) /// Thrown when doesn't match . /// /// Thrown when a parameter is invalid. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlyMemory2D(T[,,] array, int depth, int row, int column, int height, int width) { if (array.IsCovariant()) @@ -309,7 +305,6 @@ public ReadOnlyMemory2D(T[,,] array, int depth, int row, int column, int height, /// Thrown when either or are invalid. /// /// The total area must match the lenght of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlyMemory2D(ReadOnlyMemory memory, int height, int width) : this(memory, 0, height, width, 0) { @@ -329,7 +324,6 @@ public ReadOnlyMemory2D(ReadOnlyMemory memory, int height, int width) /// /// Thrown when the requested area is outside of bounds for . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int height, int width, int pitch) { if ((uint)offset > (uint)memory.Length) @@ -394,6 +388,50 @@ private ReadOnlyMemory2D(object instance, IntPtr offset, int height, int width, this.pitch = pitch; } + /// + /// Creates a new instance from an arbitrary object. + /// + /// The instance holding the data to map. + /// The target reference to point to (it must be within ). + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map. + /// A instaance with the specified parameters. + /// The parameter is not validated, and it's responsability of the caller to ensure it's valid. + /// + /// Thrown when is of an unsupported type. + /// + /// + /// Thrown when one of the input parameters is out of range. + /// + [Pure] + public static ReadOnlyMemory2D DangerousCreate(object instance, ref T value, int height, int width, int pitch) + { + if (instance.GetType() == typeof(ReadOnlyMemory)) + { + ThrowHelper.ThrowArgumentExceptionForUnsupportedType(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + IntPtr offset = instance.DangerousGetObjectDataByteOffset(ref value); + + return new ReadOnlyMemory2D(instance, offset, height, width, pitch); + } + /// /// Gets an empty instance. /// @@ -457,7 +495,6 @@ public ReadOnlySpan2D Span } else { - // The only other possible cases is with the instance being an array ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset); return new ReadOnlySpan2D(r0, this.height, this.width, this.pitch); From 261efd09be9486af73579d414a342f6b8a9bb9ce Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 12:25:18 +0200 Subject: [PATCH 067/200] Removed redundant NullReferenceExceptions --- .../Extensions/ArrayExtensions.2D.cs | 24 ------------------- .../Extensions/ArrayExtensions.3D.cs | 18 -------------- 2 files changed, 42 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 191b5ea8167..9577ca45f83 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -232,17 +232,11 @@ public static RefEnumerable GetColumn(this T[,] array, int column) /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. - /// Thrown when is . /// A instance with the values of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span2D AsSpan2D(this T[,] array) { - if (array is null) - { - throw new NullReferenceException(); - } - return new Span2D(array); } @@ -255,7 +249,6 @@ public static Span2D AsSpan2D(this T[,] array) /// The target column to map within . /// The height to map within . /// The width to map within . - /// Thrown when is . /// /// Thrown when doesn't match . /// @@ -268,11 +261,6 @@ public static Span2D AsSpan2D(this T[,] array) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span2D AsSpan2D(this T[,] array, int row, int column, int height, int width) { - if (array is null) - { - throw new NullReferenceException(); - } - return new Span2D(array, row, column, height, width); } @@ -281,17 +269,11 @@ public static Span2D AsSpan2D(this T[,] array, int row, int column, int he /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. - /// Thrown when is . /// A instance with the values of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Memory2D AsMemory2D(this T[,] array) { - if (array is null) - { - throw new NullReferenceException(); - } - return new Memory2D(array); } @@ -304,7 +286,6 @@ public static Memory2D AsMemory2D(this T[,] array) /// The target column to map within . /// The height to map within . /// The width to map within . - /// Thrown when is . /// /// Thrown when doesn't match . /// @@ -317,11 +298,6 @@ public static Memory2D AsMemory2D(this T[,] array) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Memory2D AsMemory2D(this T[,] array, int row, int column, int height, int width) { - if (array is null) - { - throw new NullReferenceException(); - } - return new Memory2D(array, row, column, height, width); } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index c101ca8d166..77823b319e8 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -147,7 +147,6 @@ public static Span AsSpan(this T[,,] array) /// The type of elements in the input 3D array instance. /// The given 3D array to wrap. /// The target layer to map within . - /// Thrown when is . /// /// Thrown when doesn't match . /// @@ -157,11 +156,6 @@ public static Span AsSpan(this T[,,] array) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span GetLayerSpan(this T[,,] array, int depth) { - if (array is null) - { - throw new NullReferenceException(); - } - if ((uint)depth >= (uint)array.GetLength(0)) { throw new ArgumentOutOfRangeException(nameof(depth)); @@ -180,7 +174,6 @@ public static Span GetLayerSpan(this T[,,] array, int depth) /// The type of elements in the input 3D array instance. /// The given 3D array to wrap. /// The target layer to map within . - /// Thrown when is . /// /// Thrown when doesn't match . /// @@ -190,11 +183,6 @@ public static Span GetLayerSpan(this T[,,] array, int depth) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span2D GetLayerSpan2D(this T[,,] array, int depth) { - if (array is null) - { - throw new NullReferenceException(); - } - return new Span2D(array, depth); } @@ -204,7 +192,6 @@ public static Span2D GetLayerSpan2D(this T[,,] array, int depth) /// The type of elements in the input 3D array instance. /// The given 3D array to wrap. /// The target layer to map within . - /// Thrown when is . /// /// Thrown when doesn't match . /// @@ -214,11 +201,6 @@ public static Span2D GetLayerSpan2D(this T[,,] array, int depth) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Memory2D GetLayerMemory2D(this T[,,] array, int depth) { - if (array is null) - { - throw new NullReferenceException(); - } - return new Memory2D(array, depth); } From 73d97ec2f34e1e6d1c35f16de9a0043d31406d4a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 12:36:35 +0200 Subject: [PATCH 068/200] Removed unnecessary attributes --- Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs | 4 ---- Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index e4a0ff7585c..c619e37cea0 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -147,7 +147,6 @@ internal ReadOnlySpan2D(object instance, IntPtr offset, int height, int width, i /// Thrown when either or are invalid. /// /// The total area must match the lenght of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan2D(T[] array, int height, int width) : this(array, 0, height, width, 0) { @@ -170,7 +169,6 @@ public ReadOnlySpan2D(T[] array, int height, int width) /// /// Thrown when the requested area is outside of bounds for . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan2D(T[] array, int offset, int height, int width, int pitch) { if (array.IsCovariant()) @@ -323,7 +321,6 @@ public ReadOnlySpan2D(T[,] array, int row, int column, int height, int width) /// Thrown when doesn't match . /// /// Thrown when a parameter is invalid. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan2D(T[,,] array, int depth) { if (array.IsCovariant()) @@ -360,7 +357,6 @@ public ReadOnlySpan2D(T[,,] array, int depth) /// Thrown when doesn't match . /// /// Thrown when a parameter is invalid. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan2D(T[,,] array, int depth, int row, int column, int height, int width) { if (array.IsCovariant()) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 9a569b436f8..901576703a0 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -176,7 +176,6 @@ internal Span2D(object instance, IntPtr offset, int height, int width, int pitch /// Thrown when either or are invalid. /// /// The total area must match the lenght of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span2D(T[] array, int height, int width) : this(array, 0, height, width, 0) { @@ -199,7 +198,6 @@ public Span2D(T[] array, int height, int width) /// /// Thrown when the requested area is outside of bounds for . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span2D(T[] array, int offset, int height, int width, int pitch) { if (array.IsCovariant()) @@ -352,7 +350,6 @@ public Span2D(T[,] array, int row, int column, int height, int width) /// Thrown when doesn't match . /// /// Thrown when a parameter is invalid. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span2D(T[,,] array, int depth) { if (array.IsCovariant()) @@ -389,7 +386,6 @@ public Span2D(T[,,] array, int depth) /// Thrown when doesn't match . /// /// Thrown when a parameter is invalid. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span2D(T[,,] array, int depth, int row, int column, int height, int width) { if (array.IsCovariant()) From d7dc9eb3d9dba71a710795bc00c4399f599ecf1b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 13:16:19 +0200 Subject: [PATCH 069/200] Added [ReadOnly]Span2D.DangerousGetReferenceAt --- .../Memory/ReadOnlySpan2D{T}.cs | 20 +++++++++++++++++++ .../Memory/Span2D{T}.cs | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index c619e37cea0..95904aac3fe 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -641,6 +641,26 @@ public ref T DangerousGetReference() #endif } + /// + /// Returns a reference to a specified element within the current instance, with no bounds check. + /// + /// The target row to get the element from. + /// The target column to get the element from. + /// A reference to the element at the specified indices. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T DangerousGetReferenceAt(int i, int j) + { +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref MemoryMarshal.GetReference(this.span); +#else + ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); +#endif + int index = (i * (this.width + this.pitch)) + j; + + return ref Unsafe.Add(ref r0, index); + } + /// /// Slices the current instance with the specified parameters. /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 901576703a0..7b5bcfeb119 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -725,6 +725,26 @@ public ref T DangerousGetReference() #endif } + /// + /// Returns a reference to a specified element within the current instance, with no bounds check. + /// + /// The target row to get the element from. + /// The target column to get the element from. + /// A reference to the element at the specified indices. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T DangerousGetReferenceAt(int i, int j) + { +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref MemoryMarshal.GetReference(this.span); +#else + ref T r0 = ref this.Instance!.DangerousGetObjectDataReferenceAt(this.Offset); +#endif + int index = (i * (this.width + this.Pitch)) + j; + + return ref Unsafe.Add(ref r0, index); + } + /// /// Slices the current instance with the specified parameters. /// From 6e35468ff2dc12bc5624f08bb991ffe04baaaff4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 13:25:43 +0200 Subject: [PATCH 070/200] Added ParallelHelper APIs for new 2D types --- .../ParallelHelper.ForEach.IInAction2D.cs | 158 ++++++++++++++++++ .../ParallelHelper.ForEach.IRefAction2D.cs | 158 ++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs create mode 100644 Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs new file mode 100644 index 00000000000..2abd685f4d4 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs @@ -0,0 +1,158 @@ +// 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.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.Toolkit.HighPerformance.Memory; + +namespace Microsoft.Toolkit.HighPerformance.Helpers +{ + /// + /// Helpers to work with parallel code in a highly optimized manner. + /// + public static partial class ParallelHelper + { + /// + /// Executes a specified action in an optimized parallel loop over the input data. + /// + /// The type of items to iterate over. + /// The type of action (implementing of ) to invoke over each item. + /// The input representing the data to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ForEach(ReadOnlyMemory2D memory) + where TAction : struct, IInAction + { + ForEach(memory, default(TAction), 1); + } + + /// + /// Executes a specified action in an optimized parallel loop over the input data. + /// + /// The type of items to iterate over. + /// The type of action (implementing of ) to invoke over each item. + /// The input representing the data to process. + /// + /// The minimum number of actions to run per individual thread. Set to 1 if all invocations + /// should be parallelized, or to a greater number if each individual invocation is fast + /// enough that it is more efficient to set a lower bound per each running thread. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ForEach(ReadOnlyMemory2D memory, int minimumActionsPerThread) + where TAction : struct, IInAction + { + ForEach(memory, default(TAction), minimumActionsPerThread); + } + + /// + /// Executes a specified action in an optimized parallel loop over the input data. + /// + /// The type of items to iterate over. + /// The type of action (implementing of ) to invoke over each item. + /// The input representing the data to process. + /// The instance representing the action to invoke. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ForEach(ReadOnlyMemory2D memory, in TAction action) + where TAction : struct, IInAction + { + ForEach(memory, action, 1); + } + + /// + /// Executes a specified action in an optimized parallel loop over the input data. + /// + /// The type of items to iterate over. + /// The type of action (implementing of ) to invoke over each item. + /// The input representing the data to process. + /// The instance representing the action to invoke. + /// + /// The minimum number of actions to run per individual thread. Set to 1 if all invocations + /// should be parallelized, or to a greater number if each individual invocation is fast + /// enough that it is more efficient to set a lower bound per each running thread. + /// + public static void ForEach(ReadOnlyMemory2D memory, in TAction action, int minimumActionsPerThread) + where TAction : struct, IInAction + { + if (minimumActionsPerThread <= 0) + { + ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread(); + } + + if (memory.IsEmpty) + { + return; + } + + int + maxBatches = 1 + ((memory.Size - 1) / minimumActionsPerThread), + clipBatches = Math.Min(maxBatches, memory.Height), + cores = Environment.ProcessorCount, + numBatches = Math.Min(clipBatches, cores), + batchHeight = 1 + ((memory.Height - 1) / numBatches); + + var actionInvoker = new InActionInvokerWithReaadOnlyMemory2D(batchHeight, memory, action); + + // Skip the parallel invocation when possible + if (numBatches == 1) + { + actionInvoker.Invoke(0); + + return; + } + + // Run the batched operations in parallel + Parallel.For( + 0, + numBatches, + new ParallelOptions { MaxDegreeOfParallelism = numBatches }, + actionInvoker.Invoke); + } + + // Wrapping struct acting as explicit closure to execute the processing batches + private readonly struct InActionInvokerWithReaadOnlyMemory2D + where TAction : struct, IInAction + { + private readonly int batchHeight; + private readonly ReadOnlyMemory2D memory; + private readonly TAction action; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public InActionInvokerWithReaadOnlyMemory2D( + int batchHeight, + ReadOnlyMemory2D memory, + in TAction action) + { + this.batchHeight = batchHeight; + this.memory = memory; + this.action = action; + } + + /// + /// Processes the batch of actions at a specified index + /// + /// The index of the batch to process + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Invoke(int i) + { + int + lowY = i * this.batchHeight, + highY = lowY + this.batchHeight, + stopY = Math.Min(highY, this.memory.Height), + width = this.memory.Width; + + ReadOnlySpan2D span = this.memory.Span; + + for (int y = lowY; y < stopY; y++) + { + ref TItem r0 = ref span.DangerousGetReferenceAt(y, 0); + + for (int x = 0; x < width; x++) + { + Unsafe.AsRef(this.action).Invoke(Unsafe.Add(ref r0, x)); + } + } + } + } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs new file mode 100644 index 00000000000..dd517233a75 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs @@ -0,0 +1,158 @@ +// 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.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.Toolkit.HighPerformance.Memory; + +namespace Microsoft.Toolkit.HighPerformance.Helpers +{ + /// + /// Helpers to work with parallel code in a highly optimized manner. + /// + public static partial class ParallelHelper + { + /// + /// Executes a specified action in an optimized parallel loop over the input data. + /// + /// The type of items to iterate over. + /// The type of action (implementing of ) to invoke over each item. + /// The input representing the data to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ForEach(Memory2D memory) + where TAction : struct, IRefAction + { + ForEach(memory, default(TAction), 1); + } + + /// + /// Executes a specified action in an optimized parallel loop over the input data. + /// + /// The type of items to iterate over. + /// The type of action (implementing of ) to invoke over each item. + /// The input representing the data to process. + /// + /// The minimum number of actions to run per individual thread. Set to 1 if all invocations + /// should be parallelized, or to a greater number if each individual invocation is fast + /// enough that it is more efficient to set a lower bound per each running thread. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ForEach(Memory2D memory, int minimumActionsPerThread) + where TAction : struct, IRefAction + { + ForEach(memory, default(TAction), minimumActionsPerThread); + } + + /// + /// Executes a specified action in an optimized parallel loop over the input data. + /// + /// The type of items to iterate over. + /// The type of action (implementing of ) to invoke over each item. + /// The input representing the data to process. + /// The instance representing the action to invoke. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ForEach(Memory2D memory, in TAction action) + where TAction : struct, IRefAction + { + ForEach(memory, action, 1); + } + + /// + /// Executes a specified action in an optimized parallel loop over the input data. + /// + /// The type of items to iterate over. + /// The type of action (implementing of ) to invoke over each item. + /// The input representing the data to process. + /// The instance representing the action to invoke. + /// + /// The minimum number of actions to run per individual thread. Set to 1 if all invocations + /// should be parallelized, or to a greater number if each individual invocation is fast + /// enough that it is more efficient to set a lower bound per each running thread. + /// + public static void ForEach(Memory2D memory, in TAction action, int minimumActionsPerThread) + where TAction : struct, IRefAction + { + if (minimumActionsPerThread <= 0) + { + ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread(); + } + + if (memory.IsEmpty) + { + return; + } + + int + maxBatches = 1 + ((memory.Size - 1) / minimumActionsPerThread), + clipBatches = Math.Min(maxBatches, memory.Height), + cores = Environment.ProcessorCount, + numBatches = Math.Min(clipBatches, cores), + batchHeight = 1 + ((memory.Height - 1) / numBatches); + + var actionInvoker = new RefActionInvokerWithReaadOnlyMemory2D(batchHeight, memory, action); + + // Skip the parallel invocation when possible + if (numBatches == 1) + { + actionInvoker.Invoke(0); + + return; + } + + // Run the batched operations in parallel + Parallel.For( + 0, + numBatches, + new ParallelOptions { MaxDegreeOfParallelism = numBatches }, + actionInvoker.Invoke); + } + + // Wrapping struct acting as explicit closure to execute the processing batches + private readonly struct RefActionInvokerWithReaadOnlyMemory2D + where TAction : struct, IRefAction + { + private readonly int batchHeight; + private readonly Memory2D memory; + private readonly TAction action; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RefActionInvokerWithReaadOnlyMemory2D( + int batchHeight, + Memory2D memory, + in TAction action) + { + this.batchHeight = batchHeight; + this.memory = memory; + this.action = action; + } + + /// + /// Processes the batch of actions at a specified index + /// + /// The index of the batch to process + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Invoke(int i) + { + int + lowY = i * this.batchHeight, + highY = lowY + this.batchHeight, + stopY = Math.Min(highY, this.memory.Height), + width = this.memory.Width; + + ReadOnlySpan2D span = this.memory.Span; + + for (int y = lowY; y < stopY; y++) + { + ref TItem r0 = ref span.DangerousGetReferenceAt(y, 0); + + for (int x = 0; x < width; x++) + { + Unsafe.AsRef(this.action).Invoke(ref Unsafe.Add(ref r0, x)); + } + } + } + } + } +} From d7a16797baf5a8faa6550101bcec2a9ce899ca67 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 14:11:01 +0200 Subject: [PATCH 071/200] Fixed a bug in T[,] array memory/span constructors --- Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs | 2 +- Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs | 2 +- Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs | 2 +- Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 59758764f2b..db4c7081785 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -208,7 +208,7 @@ public Memory2D(T[,] array, int row, int column, int height, int width) this.instance = array; this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column)); - this.height = array.GetLength(0); + this.height = height; this.width = width; this.pitch = columns - width; } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index cd3dcb0e787..48b7df240fc 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -205,7 +205,7 @@ public ReadOnlyMemory2D(T[,] array, int row, int column, int height, int width) this.instance = array; this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column)); - this.height = array.GetLength(0); + this.height = height; this.width = width; this.pitch = columns - width; } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 95904aac3fe..4557543fdc0 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -306,7 +306,7 @@ public ReadOnlySpan2D(T[,] array, int row, int column, int height, int width) #else this.instance = array; this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column)); - this.height = array.GetLength(0); + this.height = height; #endif this.width = width; this.pitch = columns - width; diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 7b5bcfeb119..f59b34b88b1 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -335,7 +335,7 @@ public Span2D(T[,] array, int row, int column, int height, int width) #else this.Instance = array; this.Offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column)); - this.height = array.GetLength(0); + this.height = height; #endif this.width = width; this.Pitch = columns - width; From 365bbc7576385ec30b1f498001573fba1fe3fdfc Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 14:11:17 +0200 Subject: [PATCH 072/200] Added ParallelHelper.ForEach2D readonly tests --- .../Test_ParallelHelper.ForEach.In2D.cs | 79 +++++++++++++++++++ ...UnitTests.HighPerformance.Shared.projitems | 1 + 2 files changed, 80 insertions(+) create mode 100644 UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In2D.cs diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In2D.cs new file mode 100644 index 00000000000..f7b7619de59 --- /dev/null +++ b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In2D.cs @@ -0,0 +1,79 @@ +// 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.Threading; +using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance.Helpers; +using Microsoft.Toolkit.HighPerformance.Memory; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.HighPerformance.Helpers +{ + public partial class Test_ParallelHelper + { + [TestCategory("ParallelHelper")] + [TestMethod] + [DataRow(1, 1, 0, 0, 1, 1)] + [DataRow(1, 2, 0, 0, 1, 2)] + [DataRow(2, 3, 0, 0, 2, 3)] + [DataRow(2, 3, 0, 1, 2, 2)] + [DataRow(3, 3, 1, 1, 2, 2)] + [DataRow(12, 12, 2, 4, 3, 3)] + [DataRow(64, 64, 0, 0, 32, 32)] + [DataRow(64, 64, 13, 14, 23, 22)] + public unsafe void Test_ParallelHelper_ForEach_In2D( + int sizeY, + int sizeX, + int row, + int column, + int height, + int width) + { + int[,] data = CreateRandomData2D(sizeY, sizeX); + + ReadOnlyMemory2D memory = data.AsMemory2D(row, column, height, width); + + Assert.AreEqual(memory.Size, height * width); + Assert.AreEqual(memory.Height, height); + Assert.AreEqual(memory.Width, width); + + int sum = 0; + + ParallelHelper.ForEach(memory, new Summer(&sum)); + + int expected = 0; + + foreach (int n in memory.Span) + { + Interlocked.Add(ref Unsafe.AsRef(&expected), n); + } + + Assert.AreEqual(sum, expected, $"The sum doesn't match, was {sum} instead of {expected}"); + } + + /// + /// Creates a random 2D array filled with random numbers. + /// + /// The height of the array to create. + /// The width of the array to create. + /// An array of random elements. + [Pure] + private static int[,] CreateRandomData2D(int height, int width) + { + var random = new Random((height * 33) + width); + + int[,] data = new int[height, width]; + + foreach (ref int n in data.AsSpan2D()) + { + n = random.Next(0, byte.MaxValue); + } + + return data; + } + } +} diff --git a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems index 31ef4c2d868..110a64646eb 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems +++ b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems @@ -33,6 +33,7 @@ + From a2007c27f81c3859a12eb2d7331e9200003d9e27 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 15:52:42 +0200 Subject: [PATCH 073/200] Added ParallelHelper.ForEach2D ref tests --- .../Test_ParallelHelper.ForEach.Ref2D.cs | 52 +++++++++++++++++++ ...UnitTests.HighPerformance.Shared.projitems | 1 + 2 files changed, 53 insertions(+) create mode 100644 UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref2D.cs diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref2D.cs new file mode 100644 index 00000000000..86b93b24ffc --- /dev/null +++ b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref2D.cs @@ -0,0 +1,52 @@ +// 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 Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance.Helpers; +using Microsoft.Toolkit.HighPerformance.Memory; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.HighPerformance.Helpers +{ + public partial class Test_ParallelHelper + { + [TestCategory("ParallelHelper")] + [TestMethod] + [DataRow(1, 1, 0, 0, 1, 1)] + [DataRow(1, 2, 0, 0, 1, 2)] + [DataRow(2, 3, 0, 0, 2, 3)] + [DataRow(2, 3, 0, 1, 2, 2)] + [DataRow(3, 3, 1, 1, 2, 2)] + [DataRow(12, 12, 2, 4, 3, 3)] + [DataRow(64, 64, 0, 0, 32, 32)] + [DataRow(64, 64, 13, 14, 23, 22)] + public void Test_ParallelHelper_ForEach_Ref2D( + int sizeY, + int sizeX, + int row, + int column, + int height, + int width) + { + int[,] + data = CreateRandomData2D(sizeY, sizeX), + copy = (int[,])data.Clone(); + + foreach (ref int n in copy.AsSpan2D(row, column, height, width)) + { + n = unchecked(n * 397); + } + + Memory2D memory = data.AsMemory2D(row, column, height, width); + + Assert.AreEqual(memory.Size, height * width); + Assert.AreEqual(memory.Height, height); + Assert.AreEqual(memory.Width, width); + + ParallelHelper.ForEach(memory, new Multiplier(397)); + + CollectionAssert.AreEqual(data, copy); + } + } +} diff --git a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems index 110a64646eb..7fab1b81203 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems +++ b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems @@ -33,6 +33,7 @@ + From 3e68dd8d2e523286c245b28f640381fe7343320f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 16:12:01 +0200 Subject: [PATCH 074/200] Added debug views for 2D memory types --- .../ArrayPoolBufferWriterDebugView{T}.cs | 2 +- .../Views/MemoryBufferWriterDebugView{T}.cs | 2 +- .../Buffers/Views/MemoryOwnerDebugView{T}.cs | 2 +- .../Buffers/Views/SpanOwnerDebugView{T}.cs | 2 +- .../Memory/Memory2D{T}.cs | 4 +++ .../Memory/ReadOnlyMemory2D{T}.cs | 4 +++ .../Memory/ReadOnlySpan2D{T}.cs | 4 +++ .../Memory/Span2D{T}.cs | 4 +++ .../Memory/Views/Memory2DDebugView{T}.cs | 30 +++++++++++++++++++ .../Views/ReadOnlyMemory2DDebugView{T}.cs | 30 +++++++++++++++++++ .../Views/ReadOnlySpan2DDebugView{T}.cs | 30 +++++++++++++++++++ .../Memory/Views/Span2DDebugView{T}.cs | 30 +++++++++++++++++++ 12 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 Microsoft.Toolkit.HighPerformance/Memory/Views/Memory2DDebugView{T}.cs create mode 100644 Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlyMemory2DDebugView{T}.cs create mode 100644 Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlySpan2DDebugView{T}.cs create mode 100644 Microsoft.Toolkit.HighPerformance/Memory/Views/Span2DDebugView{T}.cs diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs index 216ec3d5939..1cabf8541a3 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs @@ -24,7 +24,7 @@ public ArrayPoolBufferWriterDebugView(ArrayPoolBufferWriter? arrayPoolBufferW /// /// Gets the items to display for the current instance /// - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] public T[]? Items { get; } } } diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryBufferWriterDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryBufferWriterDebugView{T}.cs index b034647d86d..a74ed9694d7 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryBufferWriterDebugView{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryBufferWriterDebugView{T}.cs @@ -24,7 +24,7 @@ public MemoryBufferWriterDebugView(MemoryBufferWriter? memoryBufferWriter) /// /// Gets the items to display for the current instance /// - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] public T[]? Items { get; } } } diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs index 980115fa426..f126e40528c 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs @@ -24,7 +24,7 @@ public MemoryOwnerDebugView(MemoryOwner? memoryOwner) /// /// Gets the items to display for the current instance /// - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] public T[]? Items { get; } } } diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/SpanOwnerDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/SpanOwnerDebugView{T}.cs index 84802700b97..330404acaec 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/Views/SpanOwnerDebugView{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/Views/SpanOwnerDebugView{T}.cs @@ -24,7 +24,7 @@ public SpanOwnerDebugView(SpanOwner spanOwner) /// /// Gets the items to display for the current instance /// - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] public T[]? Items { get; } } } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index db4c7081785..640ed2e8cf7 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -5,11 +5,13 @@ using System; using System.Buffers; using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory.Internals; +using Microsoft.Toolkit.HighPerformance.Memory.Views; namespace Microsoft.Toolkit.HighPerformance.Memory { @@ -20,6 +22,8 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// of any rank, provided that a valid series of parameters for the target memory area(s) are specified. /// /// The type of items in the current instance. + [DebuggerTypeProxy(typeof(Memory2DDebugView<>))] + [DebuggerDisplay("{ToString(),raw}")] public readonly struct Memory2D : IEquatable> { /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 48b7df240fc..540f6d75055 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -5,11 +5,13 @@ using System; using System.Buffers; using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory.Internals; +using Microsoft.Toolkit.HighPerformance.Memory.Views; namespace Microsoft.Toolkit.HighPerformance.Memory { @@ -17,6 +19,8 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// A readonly version of . /// /// The type of items in the current instance. + [DebuggerTypeProxy(typeof(ReadOnlyMemory2DDebugView<>))] + [DebuggerDisplay("{ToString(),raw}")] public readonly struct ReadOnlyMemory2D : IEquatable> { /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 4557543fdc0..a047b56c9da 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -4,11 +4,13 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory.Internals; +using Microsoft.Toolkit.HighPerformance.Memory.Views; namespace Microsoft.Toolkit.HighPerformance.Memory { @@ -16,6 +18,8 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// A readonly version of . /// /// The type of items in the current instance. + [DebuggerTypeProxy(typeof(ReadOnlySpan2DDebugView<>))] + [DebuggerDisplay("{ToString(),raw}")] public readonly ref partial struct ReadOnlySpan2D { #if SPAN_RUNTIME_SUPPORT diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index f59b34b88b1..9d6df6de582 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -4,11 +4,13 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory.Internals; +using Microsoft.Toolkit.HighPerformance.Memory.Views; namespace Microsoft.Toolkit.HighPerformance.Memory { @@ -21,6 +23,8 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// type and it is transparent to the user, but note that working over discontiguous buffers has a performance impact. /// /// The type of items in the current instance. + [DebuggerTypeProxy(typeof(Span2DDebugView<>))] + [DebuggerDisplay("{ToString(),raw}")] public readonly ref partial struct Span2D { // Let's consider a representation of a discontiguous 2D memory diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Views/Memory2DDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Views/Memory2DDebugView{T}.cs new file mode 100644 index 00000000000..02b595099bb --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Memory/Views/Memory2DDebugView{T}.cs @@ -0,0 +1,30 @@ +// 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.Diagnostics; + +namespace Microsoft.Toolkit.HighPerformance.Memory.Views +{ + /// + /// A debug proxy used to display items for the type. + /// + /// The type of items stored in the input instances. + internal sealed class Memory2DDebugView + { + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The input instance with the items to display. + public Memory2DDebugView(Memory2D memory) + { + this.Items = memory.ToArray(); + } + + /// + /// Gets the items to display for the current instance + /// + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] + public T[,]? Items { get; } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlyMemory2DDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlyMemory2DDebugView{T}.cs new file mode 100644 index 00000000000..d6c31c99e1d --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlyMemory2DDebugView{T}.cs @@ -0,0 +1,30 @@ +// 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.Diagnostics; + +namespace Microsoft.Toolkit.HighPerformance.Memory.Views +{ + /// + /// A debug proxy used to display items for the type. + /// + /// The type of items stored in the input instances. + internal sealed class ReadOnlyMemory2DDebugView + { + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The input instance with the items to display. + public ReadOnlyMemory2DDebugView(ReadOnlyMemory2D memory) + { + this.Items = memory.ToArray(); + } + + /// + /// Gets the items to display for the current instance + /// + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] + public T[,]? Items { get; } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlySpan2DDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlySpan2DDebugView{T}.cs new file mode 100644 index 00000000000..a49a3477553 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlySpan2DDebugView{T}.cs @@ -0,0 +1,30 @@ +// 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.Diagnostics; + +namespace Microsoft.Toolkit.HighPerformance.Memory.Views +{ + /// + /// A debug proxy used to display items for the type. + /// + /// The type of items stored in the input instances. + internal sealed class ReadOnlySpan2DDebugView + { + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The input instance with the items to display. + public ReadOnlySpan2DDebugView(ReadOnlySpan2D span) + { + this.Items = span.ToArray(); + } + + /// + /// Gets the items to display for the current instance + /// + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] + public T[,]? Items { get; } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Views/Span2DDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Views/Span2DDebugView{T}.cs new file mode 100644 index 00000000000..3e67ca07925 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Memory/Views/Span2DDebugView{T}.cs @@ -0,0 +1,30 @@ +// 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.Diagnostics; + +namespace Microsoft.Toolkit.HighPerformance.Memory.Views +{ + /// + /// A debug proxy used to display items for the type. + /// + /// The type of items stored in the input instances. + internal sealed class Span2DDebugView + { + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The input instance with the items to display. + public Span2DDebugView(Span2D span) + { + this.Items = span.ToArray(); + } + + /// + /// Gets the items to display for the current instance + /// + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] + public T[,]? Items { get; } + } +} From e2602917c870d24752e51558c72e57c27b2cacaf Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 16:14:27 +0200 Subject: [PATCH 075/200] Simplified debug type proxies for memory types --- .../Memory/Memory2D{T}.cs | 2 +- .../Memory/ReadOnlyMemory2D{T}.cs | 2 +- .../Memory/ReadOnlySpan2D{T}.cs | 2 +- .../Memory/Span2D{T}.cs | 2 +- .../Memory/Views/Memory2DDebugView{T}.cs | 30 ---------- .../Memory/Views/MemoryDebugView2D{T}.cs | 57 +++++++++++++++++++ .../Views/ReadOnlyMemory2DDebugView{T}.cs | 30 ---------- .../Views/ReadOnlySpan2DDebugView{T}.cs | 30 ---------- .../Memory/Views/Span2DDebugView{T}.cs | 30 ---------- 9 files changed, 61 insertions(+), 124 deletions(-) delete mode 100644 Microsoft.Toolkit.HighPerformance/Memory/Views/Memory2DDebugView{T}.cs create mode 100644 Microsoft.Toolkit.HighPerformance/Memory/Views/MemoryDebugView2D{T}.cs delete mode 100644 Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlyMemory2DDebugView{T}.cs delete mode 100644 Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlySpan2DDebugView{T}.cs delete mode 100644 Microsoft.Toolkit.HighPerformance/Memory/Views/Span2DDebugView{T}.cs diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 640ed2e8cf7..fee0b5f3dea 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -22,7 +22,7 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// of any rank, provided that a valid series of parameters for the target memory area(s) are specified. /// /// The type of items in the current instance. - [DebuggerTypeProxy(typeof(Memory2DDebugView<>))] + [DebuggerTypeProxy(typeof(MemoryDebugView2D<>))] [DebuggerDisplay("{ToString(),raw}")] public readonly struct Memory2D : IEquatable> { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 540f6d75055..c316a3a7a77 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -19,7 +19,7 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// A readonly version of . /// /// The type of items in the current instance. - [DebuggerTypeProxy(typeof(ReadOnlyMemory2DDebugView<>))] + [DebuggerTypeProxy(typeof(MemoryDebugView2D<>))] [DebuggerDisplay("{ToString(),raw}")] public readonly struct ReadOnlyMemory2D : IEquatable> { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index a047b56c9da..08c6121dc6d 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -18,7 +18,7 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// A readonly version of . /// /// The type of items in the current instance. - [DebuggerTypeProxy(typeof(ReadOnlySpan2DDebugView<>))] + [DebuggerTypeProxy(typeof(MemoryDebugView2D<>))] [DebuggerDisplay("{ToString(),raw}")] public readonly ref partial struct ReadOnlySpan2D { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 9d6df6de582..33f7bed77e2 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -23,7 +23,7 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// type and it is transparent to the user, but note that working over discontiguous buffers has a performance impact. /// /// The type of items in the current instance. - [DebuggerTypeProxy(typeof(Span2DDebugView<>))] + [DebuggerTypeProxy(typeof(MemoryDebugView2D<>))] [DebuggerDisplay("{ToString(),raw}")] public readonly ref partial struct Span2D { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Views/Memory2DDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Views/Memory2DDebugView{T}.cs deleted file mode 100644 index 02b595099bb..00000000000 --- a/Microsoft.Toolkit.HighPerformance/Memory/Views/Memory2DDebugView{T}.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Diagnostics; - -namespace Microsoft.Toolkit.HighPerformance.Memory.Views -{ - /// - /// A debug proxy used to display items for the type. - /// - /// The type of items stored in the input instances. - internal sealed class Memory2DDebugView - { - /// - /// Initializes a new instance of the class with the specified parameters. - /// - /// The input instance with the items to display. - public Memory2DDebugView(Memory2D memory) - { - this.Items = memory.ToArray(); - } - - /// - /// Gets the items to display for the current instance - /// - [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] - public T[,]? Items { get; } - } -} diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Views/MemoryDebugView2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Views/MemoryDebugView2D{T}.cs new file mode 100644 index 00000000000..9431210fc44 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Memory/Views/MemoryDebugView2D{T}.cs @@ -0,0 +1,57 @@ +// 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.Diagnostics; + +namespace Microsoft.Toolkit.HighPerformance.Memory.Views +{ + /// + /// A debug proxy used to display items in a 2D layout. + /// + /// The type of items to display. + internal sealed class MemoryDebugView2D + { + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The input instance with the items to display. + public MemoryDebugView2D(Memory2D memory) + { + this.Items = memory.ToArray(); + } + + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The input instance with the items to display. + public MemoryDebugView2D(ReadOnlyMemory2D memory) + { + this.Items = memory.ToArray(); + } + + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The input instance with the items to display. + public MemoryDebugView2D(Span2D span) + { + this.Items = span.ToArray(); + } + + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The input instance with the items to display. + public MemoryDebugView2D(ReadOnlySpan2D span) + { + this.Items = span.ToArray(); + } + + /// + /// Gets the items to display for the current instance + /// + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] + public T[,]? Items { get; } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlyMemory2DDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlyMemory2DDebugView{T}.cs deleted file mode 100644 index d6c31c99e1d..00000000000 --- a/Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlyMemory2DDebugView{T}.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Diagnostics; - -namespace Microsoft.Toolkit.HighPerformance.Memory.Views -{ - /// - /// A debug proxy used to display items for the type. - /// - /// The type of items stored in the input instances. - internal sealed class ReadOnlyMemory2DDebugView - { - /// - /// Initializes a new instance of the class with the specified parameters. - /// - /// The input instance with the items to display. - public ReadOnlyMemory2DDebugView(ReadOnlyMemory2D memory) - { - this.Items = memory.ToArray(); - } - - /// - /// Gets the items to display for the current instance - /// - [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] - public T[,]? Items { get; } - } -} diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlySpan2DDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlySpan2DDebugView{T}.cs deleted file mode 100644 index a49a3477553..00000000000 --- a/Microsoft.Toolkit.HighPerformance/Memory/Views/ReadOnlySpan2DDebugView{T}.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Diagnostics; - -namespace Microsoft.Toolkit.HighPerformance.Memory.Views -{ - /// - /// A debug proxy used to display items for the type. - /// - /// The type of items stored in the input instances. - internal sealed class ReadOnlySpan2DDebugView - { - /// - /// Initializes a new instance of the class with the specified parameters. - /// - /// The input instance with the items to display. - public ReadOnlySpan2DDebugView(ReadOnlySpan2D span) - { - this.Items = span.ToArray(); - } - - /// - /// Gets the items to display for the current instance - /// - [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] - public T[,]? Items { get; } - } -} diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Views/Span2DDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Views/Span2DDebugView{T}.cs deleted file mode 100644 index 3e67ca07925..00000000000 --- a/Microsoft.Toolkit.HighPerformance/Memory/Views/Span2DDebugView{T}.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Diagnostics; - -namespace Microsoft.Toolkit.HighPerformance.Memory.Views -{ - /// - /// A debug proxy used to display items for the type. - /// - /// The type of items stored in the input instances. - internal sealed class Span2DDebugView - { - /// - /// Initializes a new instance of the class with the specified parameters. - /// - /// The input instance with the items to display. - public Span2DDebugView(Span2D span) - { - this.Items = span.ToArray(); - } - - /// - /// Gets the items to display for the current instance - /// - [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] - public T[,]? Items { get; } - } -} From c46aba8545ca94252fbb5d83b0aa7eb9b4278e8a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 16:17:37 +0200 Subject: [PATCH 076/200] Removed more unnecessary debug proxy types --- .../Buffers/ArrayPoolBufferWriter{T}.cs | 2 +- .../Buffers/MemoryBufferWriter{T}.cs | 3 +- .../Buffers/MemoryOwner{T}.cs | 2 +- .../Buffers/SpanOwner{T}.cs | 2 +- .../ArrayPoolBufferWriterDebugView{T}.cs | 30 ---------- .../Views/MemoryBufferWriterDebugView{T}.cs | 30 ---------- .../Buffers/Views/MemoryDebugView{T}.cs | 57 +++++++++++++++++++ .../Buffers/Views/MemoryOwnerDebugView{T}.cs | 30 ---------- .../Buffers/Views/SpanOwnerDebugView{T}.cs | 30 ---------- 9 files changed, 62 insertions(+), 124 deletions(-) delete mode 100644 Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs delete mode 100644 Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryBufferWriterDebugView{T}.cs create mode 100644 Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryDebugView{T}.cs delete mode 100644 Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs delete mode 100644 Microsoft.Toolkit.HighPerformance/Buffers/Views/SpanOwnerDebugView{T}.cs diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs index e17c0c48a18..9e1c032b1f2 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs @@ -24,7 +24,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers /// the arrays in use are rented from the shared instance, /// and that is also available on .NET Standard 2.0. /// - [DebuggerTypeProxy(typeof(ArrayPoolBufferWriterDebugView<>))] + [DebuggerTypeProxy(typeof(MemoryDebugView<>))] [DebuggerDisplay("{ToString(),raw}")] public sealed class ArrayPoolBufferWriter : IBuffer, IMemoryOwner { diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs index 9203eebc182..2dca3d29c03 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; +using Microsoft.Toolkit.HighPerformance.Buffers.Views; namespace Microsoft.Toolkit.HighPerformance.Buffers { @@ -20,7 +21,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers /// instances (or objects that can be converted to a ), to ensure the data is written directly /// to the intended buffer, with no possibility of doing additional allocations or expanding the available capacity. /// - [DebuggerTypeProxy(typeof(MemoryBufferWriter<>))] + [DebuggerTypeProxy(typeof(MemoryDebugView<>))] [DebuggerDisplay("{ToString(),raw}")] public sealed class MemoryBufferWriter : IBuffer { diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs index b243a5fdb5d..7f6391a967a 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs @@ -16,7 +16,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers /// An implementation with an embedded length and a fast accessor. /// /// The type of items to store in the current instance. - [DebuggerTypeProxy(typeof(MemoryOwnerDebugView<>))] + [DebuggerTypeProxy(typeof(MemoryDebugView<>))] [DebuggerDisplay("{ToString(),raw}")] public sealed class MemoryOwner : IMemoryOwner { diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs index 481f60f8195..6db21491cc5 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs @@ -31,7 +31,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers /// Not doing so will cause the underlying buffer not to be returned to the shared pool. /// /// The type of items to store in the current instance. - [DebuggerTypeProxy(typeof(SpanOwnerDebugView<>))] + [DebuggerTypeProxy(typeof(MemoryDebugView<>))] [DebuggerDisplay("{ToString(),raw}")] public readonly ref struct SpanOwner { diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs deleted file mode 100644 index 1cabf8541a3..00000000000 --- a/Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Diagnostics; - -namespace Microsoft.Toolkit.HighPerformance.Buffers.Views -{ - /// - /// A debug proxy used to display items for the type. - /// - /// The type of items stored in the input instances. - internal sealed class ArrayPoolBufferWriterDebugView - { - /// - /// Initializes a new instance of the class with the specified parameters. - /// - /// The input instance with the items to display. - public ArrayPoolBufferWriterDebugView(ArrayPoolBufferWriter? arrayPoolBufferWriter) - { - this.Items = arrayPoolBufferWriter?.WrittenSpan.ToArray(); - } - - /// - /// Gets the items to display for the current instance - /// - [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] - public T[]? Items { get; } - } -} diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryBufferWriterDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryBufferWriterDebugView{T}.cs deleted file mode 100644 index a74ed9694d7..00000000000 --- a/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryBufferWriterDebugView{T}.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Diagnostics; - -namespace Microsoft.Toolkit.HighPerformance.Buffers.Views -{ - /// - /// A debug proxy used to display items for the type. - /// - /// The type of items stored in the input instances. - internal sealed class MemoryBufferWriterDebugView - { - /// - /// Initializes a new instance of the class with the specified parameters. - /// - /// The input instance with the items to display. - public MemoryBufferWriterDebugView(MemoryBufferWriter? memoryBufferWriter) - { - this.Items = memoryBufferWriter?.WrittenSpan.ToArray(); - } - - /// - /// Gets the items to display for the current instance - /// - [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] - public T[]? Items { get; } - } -} diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryDebugView{T}.cs new file mode 100644 index 00000000000..8dcfd81a19d --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryDebugView{T}.cs @@ -0,0 +1,57 @@ +// 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.Diagnostics; + +namespace Microsoft.Toolkit.HighPerformance.Buffers.Views +{ + /// + /// A debug proxy used to display items in a 1D layout. + /// + /// The type of items to display. + internal sealed class MemoryDebugView + { + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The input instance with the items to display. + public MemoryDebugView(ArrayPoolBufferWriter? arrayPoolBufferWriter) + { + this.Items = arrayPoolBufferWriter?.WrittenSpan.ToArray(); + } + + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The input instance with the items to display. + public MemoryDebugView(MemoryBufferWriter? memoryBufferWriter) + { + this.Items = memoryBufferWriter?.WrittenSpan.ToArray(); + } + + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The input instance with the items to display. + public MemoryDebugView(MemoryOwner? memoryOwner) + { + this.Items = memoryOwner?.Span.ToArray(); + } + + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The input instance with the items to display. + public MemoryDebugView(SpanOwner spanOwner) + { + this.Items = spanOwner.Span.ToArray(); + } + + /// + /// Gets the items to display for the current instance + /// + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] + public T[]? Items { get; } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs deleted file mode 100644 index f126e40528c..00000000000 --- a/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Diagnostics; - -namespace Microsoft.Toolkit.HighPerformance.Buffers.Views -{ - /// - /// A debug proxy used to display items for the type. - /// - /// The type of items stored in the input instances. - internal sealed class MemoryOwnerDebugView - { - /// - /// Initializes a new instance of the class with the specified parameters. - /// - /// The input instance with the items to display. - public MemoryOwnerDebugView(MemoryOwner? memoryOwner) - { - this.Items = memoryOwner?.Span.ToArray(); - } - - /// - /// Gets the items to display for the current instance - /// - [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] - public T[]? Items { get; } - } -} diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/SpanOwnerDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/SpanOwnerDebugView{T}.cs deleted file mode 100644 index 330404acaec..00000000000 --- a/Microsoft.Toolkit.HighPerformance/Buffers/Views/SpanOwnerDebugView{T}.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Diagnostics; - -namespace Microsoft.Toolkit.HighPerformance.Buffers.Views -{ - /// - /// A debug proxy used to display items for the type. - /// - /// The type of items stored in the input instances. - internal sealed class SpanOwnerDebugView - { - /// - /// Initializes a new instance of the class with the specified parameters. - /// - /// The input instance with the items to display. - public SpanOwnerDebugView(SpanOwner spanOwner) - { - this.Items = spanOwner.Span.ToArray(); - } - - /// - /// Gets the items to display for the current instance - /// - [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] - public T[]? Items { get; } - } -} From 1a6ff3eb4dbf2f54a48d8ba3a651c944737e6a03 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 16:20:05 +0200 Subject: [PATCH 077/200] Fixed a typo --- Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index c316a3a7a77..78664ce2a8d 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -611,7 +611,7 @@ public unsafe MemoryHandle Pin() { if (!(this.instance is null)) { - if (this.instance is Memory memory) + if (this.instance is ReadOnlyMemory memory) { return memory.Pin(); } From f8545ac55dbcf3a0a49bffb04f0160da49f056f1 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 16:21:49 +0200 Subject: [PATCH 078/200] Tweaked [ReadOnly]Memory2D.GetHashCode() --- Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs | 2 +- Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index fee0b5f3dea..234e8e8078c 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -704,7 +704,7 @@ public override int GetHashCode() { if (!(this.instance is null)) { -#if SPAN_RUNTIME_SUPPORT +#if !NETSTANDARD1_4 return HashCode.Combine( RuntimeHelpers.GetHashCode(this.instance), this.offset, diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 78664ce2a8d..3d8146a5752 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -709,7 +709,7 @@ public override int GetHashCode() { if (!(this.instance is null)) { -#if SPAN_RUNTIME_SUPPORT +#if !NETSTANDARD1_4 return HashCode.Combine( RuntimeHelpers.GetHashCode(this.instance), this.offset, From 73d75038865f618116116089f396eb0538a693c3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 18:35:17 +0200 Subject: [PATCH 079/200] Renamed variables in unit tests --- .../Memory/Test_Memory2D{T}.cs | 140 +++++++++--------- .../Memory/Test_ReadOnlyMemory2D{T}.cs | 140 +++++++++--------- 2 files changed, 140 insertions(+), 140 deletions(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs index d14190a0a65..6e1456f24cf 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs @@ -41,14 +41,14 @@ public void Test_Memory2DT_Array1DConstructor() 1, 2, 3, 4, 5, 6 }; - Memory2D span2d = new Memory2D(array, 1, 2, 2, 1); + Memory2D memory2d = new Memory2D(array, 1, 2, 2, 1); - Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); - Assert.AreEqual(span2d.Width, 2); - Assert.AreEqual(span2d.Height, 2); - Assert.AreEqual(span2d.Span[0, 0], 2); - Assert.AreEqual(span2d.Span[1, 1], 6); + Assert.IsFalse(memory2d.IsEmpty); + Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Width, 2); + Assert.AreEqual(memory2d.Height, 2); + Assert.AreEqual(memory2d.Span[0, 0], 2); + Assert.AreEqual(memory2d.Span[1, 1], 6); Assert.ThrowsException(() => new Memory2D(new string[1], 1, 1)); Assert.ThrowsException(() => new Memory2D(array, -99, 1, 1, 1)); @@ -68,14 +68,14 @@ public void Test_Memory2DT_Array2DConstructor_1() { 4, 5, 6 } }; - Memory2D span2d = new Memory2D(array); + Memory2D memory2d = new Memory2D(array); - Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 6); - Assert.AreEqual(span2d.Width, 3); - Assert.AreEqual(span2d.Height, 2); - Assert.AreEqual(span2d.Span[0, 1], 2); - Assert.AreEqual(span2d.Span[1, 2], 6); + Assert.IsFalse(memory2d.IsEmpty); + Assert.AreEqual(memory2d.Size, 6); + Assert.AreEqual(memory2d.Width, 3); + Assert.AreEqual(memory2d.Height, 2); + Assert.AreEqual(memory2d.Span[0, 1], 2); + Assert.AreEqual(memory2d.Span[1, 2], 6); Assert.ThrowsException(() => new Memory2D(new string[1, 2])); } @@ -90,14 +90,14 @@ public void Test_Memory2DT_Array2DConstructor_2() { 4, 5, 6 } }; - Memory2D span2d = new Memory2D(array, 0, 1, 2, 2); + Memory2D memory2d = new Memory2D(array, 0, 1, 2, 2); - Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); - Assert.AreEqual(span2d.Width, 2); - Assert.AreEqual(span2d.Height, 2); - Assert.AreEqual(span2d.Span[0, 0], 2); - Assert.AreEqual(span2d.Span[1, 1], 6); + Assert.IsFalse(memory2d.IsEmpty); + Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Width, 2); + Assert.AreEqual(memory2d.Height, 2); + Assert.AreEqual(memory2d.Span[0, 0], 2); + Assert.AreEqual(memory2d.Span[1, 1], 6); Assert.ThrowsException(() => new Memory2D(new string[1, 2], 0, 0, 2, 2)); } @@ -118,14 +118,14 @@ public void Test_Memory2DT_Array3DConstructor_1() } }; - Memory2D span2d = new Memory2D(array, 1); + Memory2D memory2d = new Memory2D(array, 1); - Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 6); - Assert.AreEqual(span2d.Width, 3); - Assert.AreEqual(span2d.Height, 2); - Assert.AreEqual(span2d.Span[0, 1], 20); - Assert.AreEqual(span2d.Span[1, 2], 60); + Assert.IsFalse(memory2d.IsEmpty); + Assert.AreEqual(memory2d.Size, 6); + Assert.AreEqual(memory2d.Width, 3); + Assert.AreEqual(memory2d.Height, 2); + Assert.AreEqual(memory2d.Span[0, 1], 20); + Assert.AreEqual(memory2d.Span[1, 2], 60); Assert.ThrowsException(() => new Memory2D(array, -1)); Assert.ThrowsException(() => new Memory2D(array, 20)); @@ -147,14 +147,14 @@ public void Test_Memory2DT_Array3DConstructor_2() } }; - Memory2D span2d = new Memory2D(array, 1, 0, 1, 2, 2); + Memory2D memory2d = new Memory2D(array, 1, 0, 1, 2, 2); - Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); - Assert.AreEqual(span2d.Width, 2); - Assert.AreEqual(span2d.Height, 2); - Assert.AreEqual(span2d.Span[0, 0], 20); - Assert.AreEqual(span2d.Span[1, 1], 60); + Assert.IsFalse(memory2d.IsEmpty); + Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Width, 2); + Assert.AreEqual(memory2d.Height, 2); + Assert.AreEqual(memory2d.Span[0, 0], 20); + Assert.AreEqual(memory2d.Span[1, 1], 60); Assert.ThrowsException(() => new Memory2D(array, -1, 1, 1, 1, 1)); Assert.ThrowsException(() => new Memory2D(array, 1, -1, 1, 1, 1)); @@ -173,14 +173,14 @@ public void Test_Memory2DT_MemoryConstructor() 1, 2, 3, 4, 5, 6 }; - Memory2D span2d = new Memory2D(memory, 1, 2, 2, 1); + Memory2D memory2d = new Memory2D(memory, 1, 2, 2, 1); - Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); - Assert.AreEqual(span2d.Width, 2); - Assert.AreEqual(span2d.Height, 2); - Assert.AreEqual(span2d.Span[0, 0], 2); - Assert.AreEqual(span2d.Span[1, 1], 6); + Assert.IsFalse(memory2d.IsEmpty); + Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Width, 2); + Assert.AreEqual(memory2d.Height, 2); + Assert.AreEqual(memory2d.Span[0, 0], 2); + Assert.AreEqual(memory2d.Span[1, 1], 6); Assert.ThrowsException(() => new Memory2D(memory, -99, 1, 1, 1)); Assert.ThrowsException(() => new Memory2D(memory, 0, -10, 1, 1)); @@ -200,9 +200,9 @@ public void Test_Memory2DT_Slice_1() { 4, 5, 6 } }; - Memory2D span2d = new Memory2D(array); + Memory2D memory2d = new Memory2D(array); - Memory2D slice1 = span2d.Slice(1, 1, 1, 2); + Memory2D slice1 = memory2d.Slice(1, 1, 1, 2); Assert.AreEqual(slice1.Size, 2); Assert.AreEqual(slice1.Height, 1); @@ -210,7 +210,7 @@ public void Test_Memory2DT_Slice_1() Assert.AreEqual(slice1.Span[0, 0], 5); Assert.AreEqual(slice1.Span[0, 1], 6); - Memory2D slice2 = span2d.Slice(0, 1, 2, 2); + Memory2D slice2 = memory2d.Slice(0, 1, 2, 2); Assert.AreEqual(slice2.Size, 4); Assert.AreEqual(slice2.Height, 2); @@ -238,9 +238,9 @@ public void Test_Memory2DT_Slice_2() { 4, 5, 6 } }; - Memory2D span2d = new Memory2D(array); + Memory2D memory2d = new Memory2D(array); - Memory2D slice1 = span2d.Slice(0, 0, 2, 2); + Memory2D slice1 = memory2d.Slice(0, 0, 2, 2); Assert.AreEqual(slice1.Size, 4); Assert.AreEqual(slice1.Height, 2); @@ -274,9 +274,9 @@ public void Test_Memory2DT_TryGetMemory_1() { 4, 5, 6 } }; - Memory2D span2d = new Memory2D(array); + Memory2D memory2d = new Memory2D(array); - bool success = span2d.TryGetMemory(out Memory memory); + bool success = memory2d.TryGetMemory(out Memory memory); Assert.IsFalse(success); Assert.IsTrue(memory.IsEmpty); @@ -288,9 +288,9 @@ public void Test_Memory2DT_TryGetMemory_2() { int[] array = { 1, 2, 3, 4 }; - Memory2D span2d = new Memory2D(array, 2, 2); + Memory2D memory2d = new Memory2D(array, 2, 2); - bool success = span2d.TryGetMemory(out Memory memory); + bool success = memory2d.TryGetMemory(out Memory memory); Assert.IsTrue(success); Assert.AreEqual(memory.Length, array.Length); @@ -304,9 +304,9 @@ public void Test_Memory2DT_TryGetMemory_3() { Memory data = new[] { 1, 2, 3, 4 }; - Memory2D span2d = new Memory2D(data, 2, 2); + Memory2D memory2d = new Memory2D(data, 2, 2); - bool success = span2d.TryGetMemory(out Memory memory); + bool success = memory2d.TryGetMemory(out Memory memory); Assert.IsTrue(success); Assert.AreEqual(memory.Length, data.Length); @@ -320,9 +320,9 @@ public unsafe void Test_Memory2DT_Pin_1() { int[] array = { 1, 2, 3, 4 }; - Memory2D span2d = new Memory2D(array, 2, 2); + Memory2D memory2d = new Memory2D(array, 2, 2); - using var pin = span2d.Pin(); + using var pin = memory2d.Pin(); Assert.AreEqual(((int*)pin.Pointer)[0], 1); Assert.AreEqual(((int*)pin.Pointer)[3], 4); @@ -334,9 +334,9 @@ public unsafe void Test_Memory2DT_Pin_2() { int[] array = { 1, 2, 3, 4 }; - Memory2D span2d = new Memory2D(array, 2, 2); + Memory2D memory2d = new Memory2D(array, 2, 2); - using var pin = span2d.Pin(); + using var pin = memory2d.Pin(); Assert.AreEqual(((int*)pin.Pointer)[0], 1); Assert.AreEqual(((int*)pin.Pointer)[3], 4); @@ -352,9 +352,9 @@ public void Test_Memory2DT_ToArray_1() { 4, 5, 6 } }; - Memory2D span2d = new Memory2D(array); + Memory2D memory2d = new Memory2D(array); - int[,] copy = span2d.ToArray(); + int[,] copy = memory2d.ToArray(); Assert.AreEqual(copy.GetLength(0), array.GetLength(0)); Assert.AreEqual(copy.GetLength(1), array.GetLength(1)); @@ -372,9 +372,9 @@ public void Test_Memory2DT_ToArray_2() { 4, 5, 6 } }; - Memory2D span2d = new Memory2D(array, 0, 0, 2, 2); + Memory2D memory2d = new Memory2D(array, 0, 0, 2, 2); - int[,] copy = span2d.ToArray(); + int[,] copy = memory2d.ToArray(); Assert.AreEqual(copy.GetLength(0), 2); Assert.AreEqual(copy.GetLength(1), 2); @@ -398,12 +398,12 @@ public void Test_Memory2DT_Equals() { 4, 5, 6 } }; - Memory2D span2d = new Memory2D(array); + Memory2D memory2d = new Memory2D(array); - Assert.IsFalse(span2d.Equals(null)); - Assert.IsFalse(span2d.Equals(new Memory2D(array, 0, 1, 2, 2))); - Assert.IsTrue(span2d.Equals(new Memory2D(array))); - Assert.IsTrue(span2d.Equals(span2d)); + Assert.IsFalse(memory2d.Equals(null)); + Assert.IsFalse(memory2d.Equals(new Memory2D(array, 0, 1, 2, 2))); + Assert.IsTrue(memory2d.Equals(new Memory2D(array))); + Assert.IsTrue(memory2d.Equals(memory2d)); } [TestCategory("Memory2DT")] @@ -418,9 +418,9 @@ public void Test_Memory2DT_GetHashCode() { 4, 5, 6 } }; - Memory2D span2d = new Memory2D(array); + Memory2D memory2d = new Memory2D(array); - int a = span2d.GetHashCode(), b = span2d.GetHashCode(); + int a = memory2d.GetHashCode(), b = memory2d.GetHashCode(); Assert.AreEqual(a, b); @@ -439,9 +439,9 @@ public void Test_Memory2DT_ToString() { 4, 5, 6 } }; - Memory2D span2d = new Memory2D(array); + Memory2D memory2d = new Memory2D(array); - string text = span2d.ToString(); + string text = memory2d.ToString(); const string expected = "Microsoft.Toolkit.HighPerformance.Memory.Memory2D[2, 3]"; diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs index 886b64d25b4..09dfd67f105 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs @@ -41,14 +41,14 @@ public void Test_ReadOnlyMemory2DT_Array1DConstructor() 1, 2, 3, 4, 5, 6 }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 1, 2, 2, 1); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array, 1, 2, 2, 1); - Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); - Assert.AreEqual(span2d.Width, 2); - Assert.AreEqual(span2d.Height, 2); - Assert.AreEqual(span2d.Span[0, 0], 2); - Assert.AreEqual(span2d.Span[1, 1], 6); + Assert.IsFalse(memory2d.IsEmpty); + Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Width, 2); + Assert.AreEqual(memory2d.Height, 2); + Assert.AreEqual(memory2d.Span[0, 0], 2); + Assert.AreEqual(memory2d.Span[1, 1], 6); Assert.ThrowsException(() => new ReadOnlyMemory2D(new string[1], 1, 1)); Assert.ThrowsException(() => new ReadOnlyMemory2D(array, -99, 1, 1, 1)); @@ -68,14 +68,14 @@ public void Test_ReadOnlyMemory2DT_Array2DConstructor_1() { 4, 5, 6 } }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array); - Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 6); - Assert.AreEqual(span2d.Width, 3); - Assert.AreEqual(span2d.Height, 2); - Assert.AreEqual(span2d.Span[0, 1], 2); - Assert.AreEqual(span2d.Span[1, 2], 6); + Assert.IsFalse(memory2d.IsEmpty); + Assert.AreEqual(memory2d.Size, 6); + Assert.AreEqual(memory2d.Width, 3); + Assert.AreEqual(memory2d.Height, 2); + Assert.AreEqual(memory2d.Span[0, 1], 2); + Assert.AreEqual(memory2d.Span[1, 2], 6); Assert.ThrowsException(() => new ReadOnlyMemory2D(new string[1, 2])); } @@ -90,14 +90,14 @@ public void Test_ReadOnlyMemory2DT_Array2DConstructor_2() { 4, 5, 6 } }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 0, 1, 2, 2); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array, 0, 1, 2, 2); - Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); - Assert.AreEqual(span2d.Width, 2); - Assert.AreEqual(span2d.Height, 2); - Assert.AreEqual(span2d.Span[0, 0], 2); - Assert.AreEqual(span2d.Span[1, 1], 6); + Assert.IsFalse(memory2d.IsEmpty); + Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Width, 2); + Assert.AreEqual(memory2d.Height, 2); + Assert.AreEqual(memory2d.Span[0, 0], 2); + Assert.AreEqual(memory2d.Span[1, 1], 6); Assert.ThrowsException(() => new ReadOnlyMemory2D(new string[1, 2], 0, 0, 2, 2)); } @@ -118,14 +118,14 @@ public void Test_ReadOnlyMemory2DT_Array3DConstructor_1() } }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 1); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array, 1); - Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 6); - Assert.AreEqual(span2d.Width, 3); - Assert.AreEqual(span2d.Height, 2); - Assert.AreEqual(span2d.Span[0, 1], 20); - Assert.AreEqual(span2d.Span[1, 2], 60); + Assert.IsFalse(memory2d.IsEmpty); + Assert.AreEqual(memory2d.Size, 6); + Assert.AreEqual(memory2d.Width, 3); + Assert.AreEqual(memory2d.Height, 2); + Assert.AreEqual(memory2d.Span[0, 1], 20); + Assert.AreEqual(memory2d.Span[1, 2], 60); Assert.ThrowsException(() => new ReadOnlyMemory2D(array, -1)); Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 20)); @@ -147,14 +147,14 @@ public void Test_ReadOnlyMemory2DT_Array3DConstructor_2() } }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 1, 0, 1, 2, 2); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array, 1, 0, 1, 2, 2); - Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); - Assert.AreEqual(span2d.Width, 2); - Assert.AreEqual(span2d.Height, 2); - Assert.AreEqual(span2d.Span[0, 0], 20); - Assert.AreEqual(span2d.Span[1, 1], 60); + Assert.IsFalse(memory2d.IsEmpty); + Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Width, 2); + Assert.AreEqual(memory2d.Height, 2); + Assert.AreEqual(memory2d.Span[0, 0], 20); + Assert.AreEqual(memory2d.Span[1, 1], 60); Assert.ThrowsException(() => new ReadOnlyMemory2D(array, -1, 1, 1, 1, 1)); Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 1, -1, 1, 1, 1)); @@ -173,14 +173,14 @@ public void Test_ReadOnlyMemory2DT_ReadOnlyMemoryConstructor() 1, 2, 3, 4, 5, 6 }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(memory, 1, 2, 2, 1); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(memory, 1, 2, 2, 1); - Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); - Assert.AreEqual(span2d.Width, 2); - Assert.AreEqual(span2d.Height, 2); - Assert.AreEqual(span2d.Span[0, 0], 2); - Assert.AreEqual(span2d.Span[1, 1], 6); + Assert.IsFalse(memory2d.IsEmpty); + Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Width, 2); + Assert.AreEqual(memory2d.Height, 2); + Assert.AreEqual(memory2d.Span[0, 0], 2); + Assert.AreEqual(memory2d.Span[1, 1], 6); Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, -99, 1, 1, 1)); Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, 0, -10, 1, 1)); @@ -200,9 +200,9 @@ public void Test_ReadOnlyMemory2DT_Slice_1() { 4, 5, 6 } }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array); - ReadOnlyMemory2D slice1 = span2d.Slice(1, 1, 1, 2); + ReadOnlyMemory2D slice1 = memory2d.Slice(1, 1, 1, 2); Assert.AreEqual(slice1.Size, 2); Assert.AreEqual(slice1.Height, 1); @@ -210,7 +210,7 @@ public void Test_ReadOnlyMemory2DT_Slice_1() Assert.AreEqual(slice1.Span[0, 0], 5); Assert.AreEqual(slice1.Span[0, 1], 6); - ReadOnlyMemory2D slice2 = span2d.Slice(0, 1, 2, 2); + ReadOnlyMemory2D slice2 = memory2d.Slice(0, 1, 2, 2); Assert.AreEqual(slice2.Size, 4); Assert.AreEqual(slice2.Height, 2); @@ -238,9 +238,9 @@ public void Test_ReadOnlyMemory2DT_Slice_2() { 4, 5, 6 } }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array); - ReadOnlyMemory2D slice1 = span2d.Slice(0, 0, 2, 2); + ReadOnlyMemory2D slice1 = memory2d.Slice(0, 0, 2, 2); Assert.AreEqual(slice1.Size, 4); Assert.AreEqual(slice1.Height, 2); @@ -274,9 +274,9 @@ public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_1() { 4, 5, 6 } }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array); - bool success = span2d.TryGetMemory(out ReadOnlyMemory memory); + bool success = memory2d.TryGetMemory(out ReadOnlyMemory memory); Assert.IsFalse(success); Assert.IsTrue(memory.IsEmpty); @@ -288,9 +288,9 @@ public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_2() { int[] array = { 1, 2, 3, 4 }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 2, 2); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array, 2, 2); - bool success = span2d.TryGetMemory(out ReadOnlyMemory memory); + bool success = memory2d.TryGetMemory(out ReadOnlyMemory memory); Assert.IsTrue(success); Assert.AreEqual(memory.Length, array.Length); @@ -304,9 +304,9 @@ public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_3() { ReadOnlyMemory data = new[] { 1, 2, 3, 4 }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(data, 2, 2); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(data, 2, 2); - bool success = span2d.TryGetMemory(out ReadOnlyMemory memory); + bool success = memory2d.TryGetMemory(out ReadOnlyMemory memory); Assert.IsTrue(success); Assert.AreEqual(memory.Length, data.Length); @@ -320,9 +320,9 @@ public unsafe void Test_ReadOnlyMemory2DT_Pin_1() { int[] array = { 1, 2, 3, 4 }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 2, 2); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array, 2, 2); - using var pin = span2d.Pin(); + using var pin = memory2d.Pin(); Assert.AreEqual(((int*)pin.Pointer)[0], 1); Assert.AreEqual(((int*)pin.Pointer)[3], 4); @@ -334,9 +334,9 @@ public unsafe void Test_ReadOnlyMemory2DT_Pin_2() { int[] array = { 1, 2, 3, 4 }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 2, 2); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array, 2, 2); - using var pin = span2d.Pin(); + using var pin = memory2d.Pin(); Assert.AreEqual(((int*)pin.Pointer)[0], 1); Assert.AreEqual(((int*)pin.Pointer)[3], 4); @@ -352,9 +352,9 @@ public void Test_ReadOnlyMemory2DT_ToArray_1() { 4, 5, 6 } }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array); - int[,] copy = span2d.ToArray(); + int[,] copy = memory2d.ToArray(); Assert.AreEqual(copy.GetLength(0), array.GetLength(0)); Assert.AreEqual(copy.GetLength(1), array.GetLength(1)); @@ -372,9 +372,9 @@ public void Test_ReadOnlyMemory2DT_ToArray_2() { 4, 5, 6 } }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array, 0, 0, 2, 2); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array, 0, 0, 2, 2); - int[,] copy = span2d.ToArray(); + int[,] copy = memory2d.ToArray(); Assert.AreEqual(copy.GetLength(0), 2); Assert.AreEqual(copy.GetLength(1), 2); @@ -398,12 +398,12 @@ public void Test_ReadOnlyMemory2DT_Equals() { 4, 5, 6 } }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array); - Assert.IsFalse(span2d.Equals(null)); - Assert.IsFalse(span2d.Equals(new ReadOnlyMemory2D(array, 0, 1, 2, 2))); - Assert.IsTrue(span2d.Equals(new ReadOnlyMemory2D(array))); - Assert.IsTrue(span2d.Equals(span2d)); + Assert.IsFalse(memory2d.Equals(null)); + Assert.IsFalse(memory2d.Equals(new ReadOnlyMemory2D(array, 0, 1, 2, 2))); + Assert.IsTrue(memory2d.Equals(new ReadOnlyMemory2D(array))); + Assert.IsTrue(memory2d.Equals(memory2d)); } [TestCategory("ReadOnlyMemory2DT")] @@ -418,9 +418,9 @@ public void Test_ReadOnlyMemory2DT_GetHashCode() { 4, 5, 6 } }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array); - int a = span2d.GetHashCode(), b = span2d.GetHashCode(); + int a = memory2d.GetHashCode(), b = memory2d.GetHashCode(); Assert.AreEqual(a, b); @@ -439,9 +439,9 @@ public void Test_ReadOnlyMemory2DT_ToString() { 4, 5, 6 } }; - ReadOnlyMemory2D span2d = new ReadOnlyMemory2D(array); + ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array); - string text = span2d.ToString(); + string text = memory2d.ToString(); const string expected = "Microsoft.Toolkit.HighPerformance.Memory.ReadOnlyMemory2D[2, 3]"; From 4000d9276d7b9bf6c29125d54bb5ea48446b96e3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 19:17:10 +0200 Subject: [PATCH 080/200] Bug fixes in the GetRow/GetColumn enumerators --- .../Enumerables/RefEnumerable{T}.cs | 4 ++-- .../Extensions/ArrayExtensions.2D.cs | 10 ++++++++-- .../Extensions/Test_ArrayExtensions.2D.cs | 20 ++++--------------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 17c766f8b7c..38a89085d51 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -65,7 +65,7 @@ internal RefEnumerable(ref T reference, int length, int step) { this.span = MemoryMarshal.CreateSpan(ref reference, length * step); this.step = step; - this.position = 0; + this.position = -step; } #else /// @@ -82,7 +82,7 @@ internal RefEnumerable(object instance, IntPtr offset, int length, int step) this.offset = offset; this.length = length * step; this.step = step; - this.position = 0; + this.position = -step; } #endif diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 9577ca45f83..a3b9b9afadb 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -212,18 +212,24 @@ public static RefEnumerable GetRow(this T[,] array, int row) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RefEnumerable GetColumn(this T[,] array, int column) { + int width = array.GetLength(1); + if ((uint)column >= (uint)array.GetLength(1)) { throw new ArgumentOutOfRangeException(nameof(column)); } + int height = array.GetLength(0); + #if SPAN_RUNTIME_SUPPORT - return new RefEnumerable(ref array.DangerousGetReferenceAt(0, column), array.GetLength(0), array.GetLength(1)); + ref T r0 = ref array.DangerousGetReferenceAt(0, column); + + return new RefEnumerable(ref r0, height, width); #else ref T r0 = ref array.DangerousGetReferenceAt(0, column); IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0); - return new RefEnumerable(array, offset, array.GetLength(0), array.GetLength(1)); + return new RefEnumerable(array, offset, height, width); #endif } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs index cada6af8094..37582686d25 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs @@ -216,15 +216,9 @@ public void Test_ArrayExtensions_2D_GetRow_Rectangle() CollectionAssert.AreEqual(array.GetRow(1).ToArray(), new[] { 5, 6, 7, 8 }); - Assert.ThrowsException(() => - { - foreach (var _ in array.GetRow(-1)) { } - }); + Assert.ThrowsException(() => array.GetRow(-1)); - Assert.ThrowsException(() => - { - foreach (var _ in array.GetRow(20)) { } - }); + Assert.ThrowsException(() => array.GetRow(20)); } [TestCategory("ArrayExtensions")] @@ -257,15 +251,9 @@ public void Test_ArrayExtensions_2D_GetColumn_Rectangle() CollectionAssert.AreEqual(array.GetColumn(1).ToArray(), new[] { 2, 6, 10 }); - Assert.ThrowsException(() => - { - foreach (var _ in array.GetColumn(-1)) { } - }); + Assert.ThrowsException(() => array.GetColumn(-1)); - Assert.ThrowsException(() => - { - foreach (var _ in array.GetColumn(20)) { } - }); + Assert.ThrowsException(() => array.GetColumn(20)); } [TestCategory("ArrayExtensions")] From bb78804d3f560a11d390c3ac00b46ac138c8c202 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 19:23:31 +0200 Subject: [PATCH 081/200] Bug fixes in the ReadOnlyRefEnumerable, more tests --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 4 ++-- .../Memory/Test_ReadOnlySpan2D{T}.cs | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index dbcd0ce784e..99d73c910e6 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -65,7 +65,7 @@ internal ReadOnlyRefEnumerable(in T reference, int length, int step) { this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length * step); this.step = step; - this.position = 0; + this.position = -step; } #else /// @@ -82,7 +82,7 @@ internal ReadOnlyRefEnumerable(object instance, IntPtr offset, int length, int s this.offset = offset; this.length = length * step; this.step = step; - this.position = 0; + this.position = -step; } #endif diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs index 66787db9050..c158088e378 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Enumerables; +using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -674,6 +675,12 @@ public void Test_ReadOnlySpan2DT_GetRow() { 4, 5, 6 } }; + int i = 0; + foreach (ref readonly int value in new ReadOnlySpan2D(array).GetRow(1)) + { + Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(value), ref array[1, i++])); + } + ReadOnlyRefEnumerable enumerable = new ReadOnlySpan2D(array).GetRow(1); int[] expected = { 4, 5, 6 }; @@ -695,6 +702,12 @@ public void Test_ReadOnlySpan2DT_GetColumn() { 4, 5, 6 } }; + int i = 0; + foreach (ref readonly int value in new ReadOnlySpan2D(array).GetColumn(1)) + { + Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(value), ref array[i++, 1])); + } + ReadOnlyRefEnumerable enumerable = new ReadOnlySpan2D(array).GetColumn(2); int[] expected = { 3, 6 }; From 023aa2020d9aeaf32329fc38ace6887b6833ee5c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 19:47:37 +0200 Subject: [PATCH 082/200] Minor code tweaks --- .../Extensions/ArrayExtensions.2D.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index a3b9b9afadb..421b49a2ee0 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -159,34 +159,41 @@ public static void Fill(this T[,] array, T value, int row, int column, int wi } /// - /// Returns a over a row in a given 2D array instance. + /// 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 . /// The returned value shouldn't be used directly: use this extension in a loop. + /// Thrown when one of the input parameters is out of range. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RefEnumerable GetRow(this T[,] array, int row) { - if ((uint)row >= (uint)array.GetLength(0)) + int height = array.GetLength(0); + + if ((uint)row >= (uint)height) { throw new ArgumentOutOfRangeException(nameof(row)); } + int width = array.GetLength(1); + #if SPAN_RUNTIME_SUPPORT - return new RefEnumerable(ref array.DangerousGetReferenceAt(row, 0), array.GetLength(1), 1); + ref T r0 = ref array.DangerousGetReferenceAt(row, 0); + + return new RefEnumerable(ref r0, width, 1); #else ref T r0 = ref array.DangerousGetReferenceAt(row, 0); IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0); - return new RefEnumerable(array, offset, array.GetLength(1), 1); + return new RefEnumerable(array, offset, width, 1); #endif } /// - /// Returns an enumerable that returns the items from a given column in a given 2D array instance. + /// Returns a 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 = @@ -208,13 +215,14 @@ public static RefEnumerable GetRow(this T[,] array, int row) /// 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. + /// Thrown when one of the input parameters is out of range. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RefEnumerable GetColumn(this T[,] array, int column) { int width = array.GetLength(1); - if ((uint)column >= (uint)array.GetLength(1)) + if ((uint)column >= (uint)width) { throw new ArgumentOutOfRangeException(nameof(column)); } From 544eedc737f6edbc07dee06babd84028aada59e3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 19:47:50 +0200 Subject: [PATCH 083/200] Added T[,,].GetRow/GetColumn APIs --- .../Extensions/ArrayExtensions.3D.cs | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index 77823b319e8..754bdb5bf15 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; +using Microsoft.Toolkit.HighPerformance.Enumerables; #if SPAN_RUNTIME_SUPPORT using System.Runtime.InteropServices; #endif @@ -168,6 +169,102 @@ public static Span GetLayerSpan(this T[,,] array, int depth) } #endif + /// + /// Returns a over a row in a given 3D array instance. + /// + /// The type of elements in the input 3D array instance. + /// The input array instance. + /// The target layer to map within . + /// The target row to retrieve (0-based index). + /// A with the items from the target row within . + /// The returned value shouldn't be used directly: use this extension in a loop. + /// Thrown when one of the input parameters is out of range. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RefEnumerable GetRow(this T[,,] array, int depth, int row) + { + int + layers = array.GetLength(0), + height = array.GetLength(1); + + if ((uint)depth >= (uint)layers || + (uint)row >= (uint)height) + { + throw new ArgumentOutOfRangeException(); + } + + int width = array.GetLength(2); + +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref array.DangerousGetReferenceAt(depth, row, 0); + + return new RefEnumerable(ref r0, width, 1); +#else + ref T r0 = ref array.DangerousGetReferenceAt(depth, row, 0); + IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0); + + return new RefEnumerable(array, offset, width, 1); +#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 }, + /// { 10, 11, 12 }, + /// } + /// }; + /// + /// foreach (ref int number in matrix.GetColumn(1, 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 layer to map within . + /// 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. + /// Thrown when one of the input parameters is out of range. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RefEnumerable GetColumn(this T[,,] array, int depth, int column) + { + int + layers = array.GetLength(0), + width = array.GetLength(2); + + if ((uint)depth >= (uint)layers || + (uint)column >= (uint)width) + { + throw new ArgumentOutOfRangeException(); + } + + int height = array.GetLength(1); + +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, column); + + return new RefEnumerable(ref r0, height, width); +#else + ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, column); + IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0); + + return new RefEnumerable(array, offset, height, width); +#endif + } + /// /// Creates a new instance of the struct wrapping a layer in a 3D array. /// From fb08781b3f8288a7603ca4d298bef13bd8b49d0e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 19:52:53 +0200 Subject: [PATCH 084/200] Added new 3D array tests, minor refactoring --- ...tensions.cs => Test_ArrayExtensions.1D.cs} | 0 .../Extensions/Test_ArrayExtensions.3D.cs | 112 ++++++++++++++++++ ...UnitTests.HighPerformance.Shared.projitems | 3 +- 3 files changed, 114 insertions(+), 1 deletion(-) rename UnitTests/UnitTests.HighPerformance.Shared/Extensions/{Test_ArrayExtensions.cs => Test_ArrayExtensions.1D.cs} (100%) create mode 100644 UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.3D.cs diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.1D.cs similarity index 100% rename from UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.cs rename to UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.1D.cs diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.3D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.3D.cs new file mode 100644 index 00000000000..7ab7d67ea88 --- /dev/null +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.3D.cs @@ -0,0 +1,112 @@ +// 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.CodeAnalysis; +using System.Runtime.CompilerServices; +using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.HighPerformance.Extensions +{ + public partial class Test_ArrayExtensions + { + [TestCategory("ArrayExtensions")] + [TestMethod] + public void Test_ArrayExtensions_3D_DangerousGetReference_Int() + { + int[,,] array = new int[10, 20, 12]; + + ref int r0 = ref array.DangerousGetReference(); + ref int r1 = ref array[0, 0, 0]; + + Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1)); + } + + [TestCategory("ArrayExtensions")] + [TestMethod] + public void Test_ArrayExtensions_3D_DangerousGetReference_String() + { + string[,,] array = new string[10, 20, 12]; + + ref string r0 = ref array.DangerousGetReference(); + ref string r1 = ref array[0, 0, 0]; + + Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1)); + } + + [TestCategory("ArrayExtensions")] + [TestMethod] + public void Test_ArrayExtensions_3D_DangerousGetReferenceAt_Zero() + { + int[,,] array = new int[10, 20, 12]; + + ref int r0 = ref array.DangerousGetReferenceAt(0, 0, 0); + ref int r1 = ref array[0, 0, 0]; + + Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1)); + } + + [TestCategory("ArrayExtensions")] + [TestMethod] + public void Test_ArrayExtensions_3D_DangerousGetReferenceAt_Index() + { + int[,,] array = new int[10, 20, 12]; + + ref int r0 = ref array.DangerousGetReferenceAt(5, 3, 4); + ref int r1 = ref array[5, 3, 4]; + + Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1)); + } + + [TestCategory("ArrayExtensions")] + [TestMethod] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] + [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] + public void Test_ArrayExtensions_3D_GetRow_Rectangle() + { + int[,,] array = new int[10, 20, 12]; + + int j = 0; + foreach (ref int value in array.GetRow(4, 1)) + { + Assert.IsTrue(Unsafe.AreSame(ref value, ref array[4, 1, j++])); + } + + Assert.ThrowsException(() => array.GetRow(-1, 0)); + Assert.ThrowsException(() => array.GetRow(20, -2)); + Assert.ThrowsException(() => array.GetRow(29, 0)); + Assert.ThrowsException(() => array.GetRow(0, 55)); + } + + [TestCategory("ArrayExtensions")] + [TestMethod] + public void Test_ArrayExtensions_3D_GetRow_Empty() + { + int[,,] array = new int[0, 0, 0]; + + Assert.ThrowsException(() => array.GetRow(0, 0)); + } + + [TestCategory("ArrayExtensions")] + [TestMethod] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] + [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] + public void Test_ArrayExtensions_3D_GetColumn_Rectangle() + { + int[,,] array = new int[10, 20, 12]; + + int i = 0; + foreach (ref int value in array.GetColumn(5, 1)) + { + Assert.IsTrue(Unsafe.AreSame(ref value, ref array[5, i++, 1])); + } + + Assert.ThrowsException(() => array.GetColumn(-1, 0)); + Assert.ThrowsException(() => array.GetColumn(0, -4)); + Assert.ThrowsException(() => array.GetColumn(155, 0)); + Assert.ThrowsException(() => array.GetColumn(0, 50)); + } + } +} diff --git a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems index 7fab1b81203..6e776be38bb 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems +++ b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems @@ -16,8 +16,9 @@ + - + From 998ec84a59875c6cb634541dbe89bd41babd78ef Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 22:53:06 +0200 Subject: [PATCH 085/200] Improved codegen when throwing exceptions --- .../Extensions/ArrayExtensions.2D.cs | 12 +++++-- .../Extensions/ArrayExtensions.3D.cs | 32 +++++++++++++++---- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 421b49a2ee0..72811992134 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -175,7 +175,9 @@ public static RefEnumerable GetRow(this T[,] array, int row) if ((uint)row >= (uint)height) { - throw new ArgumentOutOfRangeException(nameof(row)); + static void Throw() => throw new ArgumentOutOfRangeException(nameof(row)); + + Throw(); } int width = array.GetLength(1); @@ -224,7 +226,9 @@ public static RefEnumerable GetColumn(this T[,] array, int column) if ((uint)column >= (uint)width) { - throw new ArgumentOutOfRangeException(nameof(column)); + static void Throw() => throw new ArgumentOutOfRangeException(nameof(column)); + + Throw(); } int height = array.GetLength(0); @@ -330,7 +334,9 @@ public static Span GetRowSpan(this T[,] array, int row) { if ((uint)row >= (uint)array.GetLength(0)) { - throw new ArgumentOutOfRangeException(nameof(row)); + static void Throw() => throw new ArgumentOutOfRangeException(nameof(row)); + + Throw(); } ref T r0 = ref array.DangerousGetReferenceAt(row, 0); diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index 754bdb5bf15..bb806ae5a9f 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -159,7 +159,9 @@ public static Span GetLayerSpan(this T[,,] array, int depth) { if ((uint)depth >= (uint)array.GetLength(0)) { - throw new ArgumentOutOfRangeException(nameof(depth)); + static void Throw() => throw new ArgumentOutOfRangeException(nameof(depth)); + + Throw(); } ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0); @@ -187,10 +189,18 @@ public static RefEnumerable GetRow(this T[,,] array, int depth, int row) layers = array.GetLength(0), height = array.GetLength(1); - if ((uint)depth >= (uint)layers || - (uint)row >= (uint)height) + if ((uint)depth >= (uint)layers) + { + static void Throw() => throw new ArgumentOutOfRangeException(nameof(depth)); + + Throw(); + } + + if ((uint)row >= (uint)height) { - throw new ArgumentOutOfRangeException(); + static void Throw() => throw new ArgumentOutOfRangeException(nameof(row)); + + Throw(); } int width = array.GetLength(2); @@ -245,10 +255,18 @@ public static RefEnumerable GetColumn(this T[,,] array, int depth, int col layers = array.GetLength(0), width = array.GetLength(2); - if ((uint)depth >= (uint)layers || - (uint)column >= (uint)width) + if ((uint)depth >= (uint)layers) { - throw new ArgumentOutOfRangeException(); + static void Throw() => throw new ArgumentOutOfRangeException(nameof(depth)); + + Throw(); + } + + if ((uint)column >= (uint)width) + { + static void Throw() => throw new ArgumentOutOfRangeException(nameof(column)); + + Throw(); } int height = array.GetLength(1); From b64827e1d649536ae813685c676fc967827bd3fd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 19 Jun 2020 23:41:24 +0200 Subject: [PATCH 086/200] Removed aaaaaaaaaaaa-s --- .../Helpers/ParallelHelper.ForEach.IInAction2D.cs | 6 +++--- .../Helpers/ParallelHelper.ForEach.IRefAction2D.cs | 6 +++--- Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs | 6 +++--- .../Memory/ReadOnlyMemory2D{T}.cs | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs index 2abd685f4d4..5f150bd767a 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs @@ -91,7 +91,7 @@ public static void ForEach(ReadOnlyMemory2D memory, in TA numBatches = Math.Min(clipBatches, cores), batchHeight = 1 + ((memory.Height - 1) / numBatches); - var actionInvoker = new InActionInvokerWithReaadOnlyMemory2D(batchHeight, memory, action); + var actionInvoker = new InActionInvokerWithReadOnlyMemory2D(batchHeight, memory, action); // Skip the parallel invocation when possible if (numBatches == 1) @@ -110,7 +110,7 @@ public static void ForEach(ReadOnlyMemory2D memory, in TA } // Wrapping struct acting as explicit closure to execute the processing batches - private readonly struct InActionInvokerWithReaadOnlyMemory2D + private readonly struct InActionInvokerWithReadOnlyMemory2D where TAction : struct, IInAction { private readonly int batchHeight; @@ -118,7 +118,7 @@ private readonly struct InActionInvokerWithReaadOnlyMemory2D private readonly TAction action; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public InActionInvokerWithReaadOnlyMemory2D( + public InActionInvokerWithReadOnlyMemory2D( int batchHeight, ReadOnlyMemory2D memory, in TAction action) diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs index dd517233a75..17e81938e54 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs @@ -91,7 +91,7 @@ public static void ForEach(Memory2D memory, in TAction ac numBatches = Math.Min(clipBatches, cores), batchHeight = 1 + ((memory.Height - 1) / numBatches); - var actionInvoker = new RefActionInvokerWithReaadOnlyMemory2D(batchHeight, memory, action); + var actionInvoker = new RefActionInvokerWithReadOnlyMemory2D(batchHeight, memory, action); // Skip the parallel invocation when possible if (numBatches == 1) @@ -110,7 +110,7 @@ public static void ForEach(Memory2D memory, in TAction ac } // Wrapping struct acting as explicit closure to execute the processing batches - private readonly struct RefActionInvokerWithReaadOnlyMemory2D + private readonly struct RefActionInvokerWithReadOnlyMemory2D where TAction : struct, IRefAction { private readonly int batchHeight; @@ -118,7 +118,7 @@ private readonly struct RefActionInvokerWithReaadOnlyMemory2D private readonly TAction action; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public RefActionInvokerWithReaadOnlyMemory2D( + public RefActionInvokerWithReadOnlyMemory2D( int batchHeight, Memory2D memory, in TAction action) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 234e8e8078c..c5e85790d5b 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -403,7 +403,7 @@ private Memory2D(object instance, IntPtr offset, int height, int width, int pitc /// The height of the 2D memory area to map. /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map. - /// A instaance with the specified parameters. + /// A instance with the specified parameters. /// The parameter is not validated, and it's responsability of the caller to ensure it's valid. /// /// Thrown when is of an unsupported type. @@ -580,7 +580,7 @@ public Memory2D Slice(int row, int column, int height, int width) /// Attempts to copy the current instance to a destination . /// /// The target of the copy operation. - /// Whether or not the operaation was successful. + /// Whether or not the operation was successful. public bool TryCopyTo(Memory destination) => Span.TryCopyTo(destination.Span); /// @@ -598,7 +598,7 @@ public Memory2D Slice(int row, int column, int height, int width) /// For this API to succeed, the target has to have the same shape as the current one. /// /// The target of the copy operation. - /// Whether or not the operaation was successful. + /// Whether or not the operation was successful. public bool TryCopyTo(Memory2D destination) => Span.TryCopyTo(destination.Span); /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 3d8146a5752..d8b5e2b3d77 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -400,7 +400,7 @@ private ReadOnlyMemory2D(object instance, IntPtr offset, int height, int width, /// The height of the 2D memory area to map. /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map. - /// A instaance with the specified parameters. + /// A instance with the specified parameters. /// The parameter is not validated, and it's responsability of the caller to ensure it's valid. /// /// Thrown when is of an unsupported type. @@ -577,7 +577,7 @@ public ReadOnlyMemory2D Slice(int row, int column, int height, int width) /// Attempts to copy the current instance to a destination . /// /// The target of the copy operation. - /// Whether or not the operaation was successful. + /// Whether or not the operation was successful. public bool TryCopyTo(Memory destination) => Span.TryCopyTo(destination.Span); /// @@ -595,7 +595,7 @@ public ReadOnlyMemory2D Slice(int row, int column, int height, int width) /// For this API to succeed, the target has to have the same shape as the current one. /// /// The target of the copy operation. - /// Whether or not the operaation was successful. + /// Whether or not the operation was successful. public bool TryCopyTo(Memory2D destination) => Span.TryCopyTo(destination.Span); /// From 243132317debb0e21f9c624a83d8b02d95b5df7a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 20 Jun 2020 00:08:24 +0200 Subject: [PATCH 087/200] Added more comments for IsCovariant() extension --- .../Extensions/ArrayExtensions.1D.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs index d9340365374..f43ddea43dc 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs @@ -204,7 +204,10 @@ public static bool IsCovariant(this T[] array) return #pragma warning disable SA1003 // Whitespace before ! operator #if NETSTANDARD1_4 - + // On .NET Standard 1.4 we use GetTypeInfo to get the info we need + // on the current type T. We only do this step when absolutely necessary + // as it incurs in more overhead. Additionally, this pattern is recognized + // as a JIT intrinsics on .NET and results in a much more efficient codegen. !System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(T)).IsValueType && #else !typeof(T).IsValueType && From 98c511ae7b6f070fa66cc0242c942278741c5056 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 20 Jun 2020 00:10:21 +0200 Subject: [PATCH 088/200] Added XML note for [ReadOnly]RefEnumerable.step --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 1 + .../Enumerables/RefEnumerable{T}.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index 99d73c910e6..6ff9b1bad39 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -23,6 +23,7 @@ public ref struct ReadOnlyRefEnumerable /// /// The distance between items in the sequence to enumerate. /// + /// The distance refers to items, not byte offset. private readonly int step; #if SPAN_RUNTIME_SUPPORT diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 38a89085d51..447aab07741 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -23,6 +23,7 @@ public ref struct RefEnumerable /// /// The distance between items in the sequence to enumerate. /// + /// The distance refers to items, not byte offset. private readonly int step; #if SPAN_RUNTIME_SUPPORT From e79c6a48d5cb34a6ee08c65fdb750517c986ee87 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 20 Jun 2020 18:49:14 +0200 Subject: [PATCH 089/200] Fixed two typos --- .../Extensions/ArrayExtensions.2D.cs | 2 +- .../Extensions/ArrayExtensions.3D.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 72811992134..11db02d4bed 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -345,7 +345,7 @@ public static Span GetRowSpan(this T[,] array, int row) } /// - /// Cretes a new over an input 2D array. + /// Creates a new over an input 2D array. /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index bb806ae5a9f..1c2a64ec01f 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -112,7 +112,7 @@ private sealed class RawArray3DData #if SPAN_RUNTIME_SUPPORT /// - /// Cretes a new over an input 3D array. + /// Creates a new over an input 3D array. /// /// The type of elements in the input 3D array instance. /// The input 3D array instance. From 5d6c69d6c8f5b26eb8da1aead50e2d5f4d021c3f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 23 Jun 2020 22:46:04 +0200 Subject: [PATCH 090/200] Readonly all the things! --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 6 +++--- .../Enumerables/ReadOnlySpanTokenizer{T}.cs | 1 + .../Enumerables/RefEnumerable{T}.cs | 6 +++--- .../Enumerables/SpanTokenizer{T}.cs | 1 + 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index 6ff9b1bad39..56b5ab7be91 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -90,7 +90,7 @@ internal ReadOnlyRefEnumerable(object instance, IntPtr offset, int length, int s /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyRefEnumerable GetEnumerator() => this; + public readonly ReadOnlyRefEnumerable GetEnumerator() => this; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -115,7 +115,7 @@ public bool MoveNext() } /// - public ref readonly T Current + public readonly ref readonly T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -133,7 +133,7 @@ public ref readonly T Current /// [Pure] - public T[] ToArray() + public readonly T[] ToArray() { #if SPAN_RUNTIME_SUPPORT // Fast path for contiguous items diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs index 37bc6168f10..a4a4dd7c4aa 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs @@ -43,6 +43,7 @@ public ref struct ReadOnlySpanTokenizer /// /// The source instance. /// The separator item to use. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpanTokenizer(ReadOnlySpan span, T separator) { this.span = span; diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 447aab07741..2c256e1100f 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -90,7 +90,7 @@ internal RefEnumerable(object instance, IntPtr offset, int length, int step) /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public RefEnumerable GetEnumerator() => this; + public readonly RefEnumerable GetEnumerator() => this; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -115,7 +115,7 @@ public bool MoveNext() } /// - public ref T Current + public readonly ref T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -142,7 +142,7 @@ public ref T Current /// ignoring the current position in case the sequence has already been enumerated in part. /// [Pure] - public T[] ToArray() + public readonly T[] ToArray() { #if SPAN_RUNTIME_SUPPORT // Fast path for contiguous items diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs index da8a9dce040..b5673f2e1c4 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs @@ -43,6 +43,7 @@ public ref struct SpanTokenizer /// /// The source instance. /// The separator item to use. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public SpanTokenizer(Span span, T separator) { this.span = span; From 5dfd5d378d463205bb203d6b37f248b42346b53a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 23 Jun 2020 23:42:47 +0200 Subject: [PATCH 091/200] Minor optimization when wrapping [ReadOnly]Memory --- .../Memory/Memory2D{T}.cs | 20 +++++++++++++++++-- .../Memory/ReadOnlyMemory2D{T}.cs | 16 +++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index c5e85790d5b..f6d2cce7326 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -369,8 +369,24 @@ public Memory2D(Memory memory, int offset, int height, int width, int pitch) ThrowHelper.ThrowArgumentException(); } - this.instance = memory.Slice(offset); - this.offset = default; + // Check if the input Memory instance wraps an array we can access. + // This is fine, since Memory on its own doesn't control the lifetime + // of the underlying array anyway, and this Memory2D type would do the same. + // Using the array directly makes retrieving a Span2D faster down the line, + // as we no longer have to jump through the boxed Memory first anymore. + if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) + { + T[] array = segment.Array!; + + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)); + } + else + { + this.instance = memory.Slice(offset); + this.offset = default; + } + this.height = height; this.width = width; this.pitch = pitch; diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index d8b5e2b3d77..285b82d22d2 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -366,8 +366,20 @@ public ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int height, int wi ThrowHelper.ThrowArgumentException(); } - this.instance = memory.Slice(offset); - this.offset = default; + // Access the array directly, if possible, just like in Memory2D + if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) + { + T[] array = segment.Array!; + + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)); + } + else + { + this.instance = memory.Slice(offset); + this.offset = default; + } + this.height = height; this.width = width; this.pitch = pitch; From 87b8d23d14b8dd5db77d31e4509016002600bf07 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 24 Jun 2020 00:09:37 +0200 Subject: [PATCH 092/200] Improved Memory support in ReadOnlyMemory2D --- .../Memory/ReadOnlyMemory2D{T}.cs | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 285b82d22d2..0cbe5b0020c 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -500,6 +500,17 @@ public ReadOnlySpan2D Span if (!(this.instance is null)) { #if SPAN_RUNTIME_SUPPORT + // Check Memory too to be extra sure (could be from DangerousCreate) + if (this.instance.GetType() == typeof(Memory)) + { + Memory memory = (Memory)this.instance; + + ref T r0 = ref memory.Span.DangerousGetReference(); + + return new ReadOnlySpan2D(r0, this.height, this.width, this.pitch); + } + + // Check the "officially" supported cases if (this.instance.GetType() == typeof(ReadOnlyMemory)) { ReadOnlyMemory memory = (ReadOnlyMemory)this.instance; @@ -511,6 +522,7 @@ public ReadOnlySpan2D Span } else { + // This handles both arrays and strings ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset); return new ReadOnlySpan2D(r0, this.height, this.width, this.pitch); @@ -565,10 +577,16 @@ public ReadOnlyMemory2D Slice(int row, int column, int height, int width) IntPtr offset = this.offset + (shift * Unsafe.SizeOf()); - if (this.instance is Memory memory) + if (this.instance!.GetType() == typeof(Memory)) { - // Memory instance are always already sliced - object instance = memory.Slice((int)offset); + object instance = ((Memory)this.instance).Slice((int)offset); + + return new ReadOnlyMemory2D(instance, default, height, width, pitch); + } + + if (this.instance.GetType() == typeof(ReadOnlyMemory)) + { + object instance = ((ReadOnlyMemory)this.instance).Slice((int)offset); return new ReadOnlyMemory2D(instance, default, height, width, pitch); } @@ -623,9 +641,14 @@ public unsafe MemoryHandle Pin() { if (!(this.instance is null)) { - if (this.instance is ReadOnlyMemory memory) + if (this.instance.GetType() == typeof(Memory)) + { + return ((Memory)this.instance).Pin(); + } + + if (this.instance.GetType() == typeof(ReadOnlyMemory)) { - return memory.Pin(); + return ((ReadOnlyMemory)this.instance).Pin(); } GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned); @@ -660,9 +683,13 @@ public bool TryGetMemory(out ReadOnlyMemory memory) memory = Unsafe.As, ReadOnlyMemory>(ref temp); } - else if (this.instance.GetType() == typeof(ReadOnlyMemory)) + else if (this.instance.GetType() == typeof(Memory)) { // If the object is a Memory, just slice it as needed + memory = ((Memory)this.instance).Slice(0, this.height * this.width); + } + else if (this.instance.GetType() == typeof(ReadOnlyMemory)) + { memory = ((ReadOnlyMemory)this.instance).Slice(0, this.height * this.width); } else if (this.instance.GetType() == typeof(T[])) From 5ee7662c95b959aae439cb762c80d04ab29b0497 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 24 Jun 2020 00:09:48 +0200 Subject: [PATCH 093/200] Added string constructors in ReadOnlyMemory2D --- .../Memory/ReadOnlyMemory2D{T}.cs | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 0cbe5b0020c..e4581269b96 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -48,6 +48,80 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// private readonly int pitch; + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. + /// + /// Thrown when either or are invalid. + /// + /// The total area must match the lenght of . + public ReadOnlyMemory2D(string text, int height, int width) + : this(text, 0, height, width, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// 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. + /// + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . + /// + public ReadOnlyMemory2D(string text, int offset, int height, int width, int pitch) + { + if ((uint)offset > (uint)text.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + if (width == 0 || height == 0) + { + this = default; + + return; + } + + int + remaining = text.Length - offset, + area = ((width + pitch) * (height - 1)) + width; + + if (area > remaining) + { + ThrowHelper.ThrowArgumentException(); + } + + this.instance = text; + this.offset = text.DangerousGetObjectDataByteOffset(ref text.DangerousGetReferenceAt(offset)); + this.height = height; + this.width = width; + this.pitch = pitch; + } + /// /// Initializes a new instance of the struct. /// From 6c682a950e5ccb26fcceeed5ebe88a636f6f345f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 24 Jun 2020 00:17:43 +0200 Subject: [PATCH 094/200] Removed and fixed Memory support in 2D memory --- .../Memory/ReadOnlyMemory2D{T}.cs | 36 ++++--------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index e4581269b96..ef841af76ec 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -497,6 +497,11 @@ private ReadOnlyMemory2D(object instance, IntPtr offset, int height, int width, [Pure] public static ReadOnlyMemory2D DangerousCreate(object instance, ref T value, int height, int width, int pitch) { + if (instance.GetType() == typeof(Memory)) + { + ThrowHelper.ThrowArgumentExceptionForUnsupportedType(); + } + if (instance.GetType() == typeof(ReadOnlyMemory)) { ThrowHelper.ThrowArgumentExceptionForUnsupportedType(); @@ -574,17 +579,6 @@ public ReadOnlySpan2D Span if (!(this.instance is null)) { #if SPAN_RUNTIME_SUPPORT - // Check Memory too to be extra sure (could be from DangerousCreate) - if (this.instance.GetType() == typeof(Memory)) - { - Memory memory = (Memory)this.instance; - - ref T r0 = ref memory.Span.DangerousGetReference(); - - return new ReadOnlySpan2D(r0, this.height, this.width, this.pitch); - } - - // Check the "officially" supported cases if (this.instance.GetType() == typeof(ReadOnlyMemory)) { ReadOnlyMemory memory = (ReadOnlyMemory)this.instance; @@ -651,14 +645,7 @@ public ReadOnlyMemory2D Slice(int row, int column, int height, int width) IntPtr offset = this.offset + (shift * Unsafe.SizeOf()); - if (this.instance!.GetType() == typeof(Memory)) - { - object instance = ((Memory)this.instance).Slice((int)offset); - - return new ReadOnlyMemory2D(instance, default, height, width, pitch); - } - - if (this.instance.GetType() == typeof(ReadOnlyMemory)) + if (this.instance!.GetType() == typeof(ReadOnlyMemory)) { object instance = ((ReadOnlyMemory)this.instance).Slice((int)offset); @@ -715,11 +702,6 @@ public unsafe MemoryHandle Pin() { if (!(this.instance is null)) { - if (this.instance.GetType() == typeof(Memory)) - { - return ((Memory)this.instance).Pin(); - } - if (this.instance.GetType() == typeof(ReadOnlyMemory)) { return ((ReadOnlyMemory)this.instance).Pin(); @@ -757,13 +739,9 @@ public bool TryGetMemory(out ReadOnlyMemory memory) memory = Unsafe.As, ReadOnlyMemory>(ref temp); } - else if (this.instance.GetType() == typeof(Memory)) - { - // If the object is a Memory, just slice it as needed - memory = ((Memory)this.instance).Slice(0, this.height * this.width); - } else if (this.instance.GetType() == typeof(ReadOnlyMemory)) { + // If the object is a ReadOnlyMemory, just slice it as needed memory = ((ReadOnlyMemory)this.instance).Slice(0, this.height * this.width); } else if (this.instance.GetType() == typeof(T[])) From e7823c6ea573930eca2507173afa00f0398a43ba Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 24 Jun 2020 00:17:55 +0200 Subject: [PATCH 095/200] Added support for string in Memory2D --- .../Memory/Memory2D{T}.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index f6d2cce7326..5fd7d1a1631 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -435,6 +435,11 @@ public static Memory2D DangerousCreate(object instance, ref T value, int heig ThrowHelper.ThrowArgumentExceptionForUnsupportedType(); } + if (instance.GetType() == typeof(ReadOnlyMemory)) + { + ThrowHelper.ThrowArgumentExceptionForUnsupportedType(); + } + if (height < 0) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); @@ -659,6 +664,20 @@ public bool TryGetMemory(out Memory memory) { memory = default; } + else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string)) + { + string text = Unsafe.As(this.instance); + int index = text.AsSpan().IndexOf(text.DangerousGetObjectDataReferenceAt(this.offset)); + ReadOnlyMemory temp = text.AsMemory(index, Size); + + // The string type could still be present if a user ends up creating a + // Memory2D instance from a string using DangerousCreate. Similarly to + // how CoreCLR handles the equivalent case in Memory, here we just do + // the necessary steps to still retrieve a Memory instance correctly + // wrapping the target string. In this case, it is up to the caller + // to make sure not to ever actually write to the resulting Memory. + memory = MemoryMarshal.AsMemory(Unsafe.As, Memory>(ref temp)); + } else if (this.instance.GetType() == typeof(Memory)) { // If the object is a Memory, just slice it as needed From 77ec56a3baaf5a0f1e67d059b9229f53de77f5df Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 24 Jun 2020 00:19:51 +0200 Subject: [PATCH 096/200] Fixed initial offset with array segment from memory --- Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs | 2 +- Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 5fd7d1a1631..c6b23fead59 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -379,7 +379,7 @@ public Memory2D(Memory memory, int offset, int height, int width, int pitch) T[] array = segment.Array!; this.instance = array; - this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)); + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)) + segment.Offset; } else { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index ef841af76ec..be16357e458 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -446,7 +446,7 @@ public ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int height, int wi T[] array = segment.Array!; this.instance = array; - this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)); + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)) + segment.Offset; } else { From 6696eb78cd97e9ae8e30dc71d7d50d8934bba17e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 13 Jul 2020 22:46:31 +0200 Subject: [PATCH 097/200] Improved codegen in enumerables --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 13 +++++--- .../Enumerables/ReadOnlySpanEnumerable{T}.cs | 20 ++++++++---- .../Enumerables/RefEnumerable{T}.cs | 13 +++++--- .../Enumerables/SpanEnumerable{T}.cs | 32 +++++++++++-------- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index 56b5ab7be91..14b716fd5f4 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -123,17 +123,20 @@ public readonly ref readonly T Current #if SPAN_RUNTIME_SUPPORT return ref this.span.DangerousGetReferenceAt(this.position); #else - ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); - ref T ri = ref Unsafe.Add(ref r0, this.position); + unsafe + { + ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.position); - return ref ri; + return ref ri; + } #endif } } /// [Pure] - public readonly T[] ToArray() + public readonly unsafe T[] ToArray() { #if SPAN_RUNTIME_SUPPORT // Fast path for contiguous items @@ -164,7 +167,7 @@ public readonly T[] ToArray() for (int i = 0, j = 0; i < length; i += this.step, j++) { - Unsafe.Add(ref destinationRef, j) = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destinationRef, (IntPtr)(void*)(uint)j) = Unsafe.Add(ref sourceRef, (IntPtr)(void*)(uint)i); } return array; diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs index c732861d670..feba42c8db8 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs @@ -75,11 +75,14 @@ public readonly Item Current get { #if SPAN_RUNTIME_SUPPORT - ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T ri = ref Unsafe.Add(ref r0, this.index); + unsafe + { + ref T r0 = ref MemoryMarshal.GetReference(this.span); + ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.index); - // See comment in SpanEnumerable about this - return new Item(ref ri, this.index); + // See comment in SpanEnumerable about this + return new Item(ref ri, this.index); + } #else return new Item(this.span, this.index); #endif @@ -138,10 +141,13 @@ public ref readonly T Value #if SPAN_RUNTIME_SUPPORT return ref MemoryMarshal.GetReference(this.span); #else - ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T ri = ref Unsafe.Add(ref r0, this.index); + unsafe + { + ref T r0 = ref MemoryMarshal.GetReference(this.span); + ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.index); - return ref ri; + return ref ri; + } #endif } } diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 2c256e1100f..62228cd16db 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -123,10 +123,13 @@ public readonly ref T Current #if SPAN_RUNTIME_SUPPORT return ref this.span.DangerousGetReferenceAt(this.position); #else - ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); - ref T ri = ref Unsafe.Add(ref r0, this.position); + unsafe + { + ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.position); - return ref ri; + return ref ri; + } #endif } } @@ -142,7 +145,7 @@ public readonly ref T Current /// ignoring the current position in case the sequence has already been enumerated in part. /// [Pure] - public readonly T[] ToArray() + public readonly unsafe T[] ToArray() { #if SPAN_RUNTIME_SUPPORT // Fast path for contiguous items @@ -173,7 +176,7 @@ public readonly T[] ToArray() for (int i = 0, j = 0; i < length; i += this.step, j++) { - Unsafe.Add(ref destinationRef, j) = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destinationRef, (IntPtr)(void*)(uint)j) = Unsafe.Add(ref sourceRef, (IntPtr)(void*)(uint)i); } return array; diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs index 23cbffac668..9811c24770a 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs @@ -75,16 +75,19 @@ public readonly Item Current get { #if SPAN_RUNTIME_SUPPORT - ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T ri = ref Unsafe.Add(ref r0, this.index); - - // On .NET Standard 2.1 and .NET Core (or on any target that offers runtime - // support for the Span types), we can save 4 bytes by piggybacking the - // current index in the length of the wrapped span. We're going to use the - // first item as the target reference, and the length as a host for the - // current original offset. This is not possible on eg. .NET Standard 2.0, - // as we lack the API to create Span-s from arbitrary references. - return new Item(ref ri, this.index); + unsafe + { + ref T r0 = ref MemoryMarshal.GetReference(this.span); + ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.index); + + // On .NET Standard 2.1 and .NET Core (or on any target that offers runtime + // support for the Span types), we can save 4 bytes by piggybacking the + // current index in the length of the wrapped span. We're going to use the + // first item as the target reference, and the length as a host for the + // current original offset. This is not possible on eg. .NET Standard 2.0, + // as we lack the API to create Span-s from arbitrary references. + return new Item(ref ri, this.index); + } #else return new Item(this.span, this.index); #endif @@ -143,10 +146,13 @@ public ref T Value #if SPAN_RUNTIME_SUPPORT return ref MemoryMarshal.GetReference(this.span); #else - ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T ri = ref Unsafe.Add(ref r0, this.index); + unsafe + { + ref T r0 = ref MemoryMarshal.GetReference(this.span); + ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.index); - return ref ri; + return ref ri; + } #endif } } From 9d9ec18f36d01b8a2b737bcd0031a023465eeb71 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 13 Jul 2020 22:48:39 +0200 Subject: [PATCH 098/200] Improved codegen in refactored array extensions --- .../Extensions/ArrayExtensions.3D.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index 1c2a64ec01f..e77df98b950 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -65,13 +65,13 @@ public static ref T DangerousGetReference(this T[,,] array) /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T DangerousGetReferenceAt(this T[,,] array, int i, int j, int k) + public static unsafe ref T DangerousGetReferenceAt(this T[,,] array, int i, int j, int k) { #if NETCORE_RUNTIME var arrayData = Unsafe.As(array); int offset = (i * arrayData.Height * arrayData.Width) + (j * arrayData.Width) + k; ref T r0 = ref Unsafe.As(ref arrayData.Data); - ref T ri = ref Unsafe.Add(ref r0, offset); + ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); return ref ri; #else @@ -82,10 +82,7 @@ public static ref T DangerousGetReferenceAt(this T[,,] array, int i, int j, i return ref array[i, j, k]; } - unsafe - { - return ref Unsafe.AsRef(null); - } + return ref Unsafe.AsRef(null); #endif } From 593fa47bfe7d7a213a62c2d28a7b531841932423 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 13 Jul 2020 22:49:22 +0200 Subject: [PATCH 099/200] Improved codegen in new ParallelHelper APIs --- .../Helpers/ParallelHelper.ForEach.IInAction2D.cs | 4 ++-- .../Helpers/ParallelHelper.ForEach.IRefAction2D.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs index 5f150bd767a..4fad95559ba 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs @@ -133,7 +133,7 @@ public InActionInvokerWithReadOnlyMemory2D( /// /// The index of the batch to process [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Invoke(int i) + public unsafe void Invoke(int i) { int lowY = i * this.batchHeight, @@ -149,7 +149,7 @@ public void Invoke(int i) for (int x = 0; x < width; x++) { - Unsafe.AsRef(this.action).Invoke(Unsafe.Add(ref r0, x)); + Unsafe.AsRef(this.action).Invoke(Unsafe.Add(ref r0, (IntPtr)(void*)(uint)x)); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs index 17e81938e54..16ac2191fd7 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs @@ -133,7 +133,7 @@ public RefActionInvokerWithReadOnlyMemory2D( /// /// The index of the batch to process [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Invoke(int i) + public unsafe void Invoke(int i) { int lowY = i * this.batchHeight, @@ -149,7 +149,7 @@ public void Invoke(int i) for (int x = 0; x < width; x++) { - Unsafe.AsRef(this.action).Invoke(ref Unsafe.Add(ref r0, x)); + Unsafe.AsRef(this.action).Invoke(ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)x)); } } } From 22c4321ef8166083fe44089701b079be2a8c311c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 13 Jul 2020 22:56:18 +0200 Subject: [PATCH 100/200] Improved codegen in [ReadOnly]Span2D types --- .../Memory/ReadOnlySpan2D{T}.Enumerator.cs | 13 +++++++------ .../Memory/ReadOnlySpan2D{T}.cs | 19 +++++++++++-------- .../Memory/Span2D{T}.Enumerator.cs | 13 +++++++------ .../Memory/Span2D{T}.cs | 19 +++++++++++-------- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs index 586d1044d7c..67b1b75fef2 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs @@ -26,15 +26,16 @@ public readonly ref partial struct ReadOnlySpan2D /// The returned value shouldn't be used directly: use this extension in a loop. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyRefEnumerable GetRow(int row) + public unsafe ReadOnlyRefEnumerable GetRow(int row) { if ((uint)row >= Height) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); } + int startIndex = (this.width + this.pitch) * row; ref T r0 = ref this.DangerousGetReference(); - ref T r1 = ref Unsafe.Add(ref r0, (this.width + this.pitch) * row); + ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)startIndex); #if SPAN_RUNTIME_SUPPORT return new ReadOnlyRefEnumerable(r1, Width, 1); @@ -53,7 +54,7 @@ public ReadOnlyRefEnumerable GetRow(int row) /// The returned value shouldn't be used directly: use this extension in a loop. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyRefEnumerable GetColumn(int column) + public unsafe ReadOnlyRefEnumerable GetColumn(int column) { if ((uint)column >= Width) { @@ -61,7 +62,7 @@ public ReadOnlyRefEnumerable GetColumn(int column) } ref T r0 = ref this.DangerousGetReference(); - ref T r1 = ref Unsafe.Add(ref r0, column); + ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)column); #if SPAN_RUNTIME_SUPPORT return new ReadOnlyRefEnumerable(r1, Height, this.width + this.pitch); @@ -188,7 +189,7 @@ public bool MoveNext() /// /// Gets the duck-typed property. /// - public ref readonly T Current + public readonly unsafe ref readonly T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -200,7 +201,7 @@ public ref readonly T Current #endif int index = (this.y * (this.width + this.pitch)) + this.x; - return ref Unsafe.Add(ref r0, index); + return ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 08c6121dc6d..d142156ce3e 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -465,7 +465,7 @@ public int Width /// /// Thrown when either or are invalid. /// - public ref readonly T this[int i, int j] + public unsafe ref readonly T this[int i, int j] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -483,7 +483,7 @@ public int Width #endif int index = (i * (this.width + this.pitch)) + j; - return ref Unsafe.Add(ref r0, index); + return ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); } } @@ -653,7 +653,7 @@ public ref T DangerousGetReference() /// A reference to the element at the specified indices. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T DangerousGetReferenceAt(int i, int j) + public unsafe ref T DangerousGetReferenceAt(int i, int j) { #if SPAN_RUNTIME_SUPPORT ref T r0 = ref MemoryMarshal.GetReference(this.span); @@ -662,7 +662,7 @@ public ref T DangerousGetReferenceAt(int i, int j) #endif int index = (i * (this.width + this.pitch)) + j; - return ref Unsafe.Add(ref r0, index); + return ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); } /// @@ -705,9 +705,12 @@ public ReadOnlySpan2D Slice(int row, int column, int width, int height) pitch = this.pitch + (this.width - width); #if SPAN_RUNTIME_SUPPORT - ref T r0 = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.span), shift); + unsafe + { + ref T r0 = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.span), (IntPtr)(void*)(uint)shift); - return new ReadOnlySpan2D(r0, height, width, pitch); + return new ReadOnlySpan2D(r0, height, width, pitch); + } #else IntPtr offset = this.offset + (shift * Unsafe.SizeOf()); @@ -723,7 +726,7 @@ public ReadOnlySpan2D Slice(int row, int column, int width, int height) /// Throw when is out of range. /// The resulting row . [Pure] - public ReadOnlySpan GetRowSpan(int row) + public unsafe ReadOnlySpan GetRowSpan(int row) { if ((uint)row >= (uint)Height) { @@ -732,7 +735,7 @@ public ReadOnlySpan GetRowSpan(int row) int offset = (this.width + this.pitch) * row; ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T r1 = ref Unsafe.Add(ref r0, offset); + ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); return MemoryMarshal.CreateReadOnlySpan(ref r1, this.width); } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index c296b9ecf1d..762788e8db9 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -26,15 +26,16 @@ public readonly ref partial struct Span2D /// The returned value shouldn't be used directly: use this extension in a loop. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public RefEnumerable GetRow(int row) + public unsafe RefEnumerable GetRow(int row) { if ((uint)row >= Height) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); } + int startIndex = (this.width + this.Pitch) * row; ref T r0 = ref this.DangerousGetReference(); - ref T r1 = ref Unsafe.Add(ref r0, (this.width + this.Pitch) * row); + ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)startIndex); #if SPAN_RUNTIME_SUPPORT return new RefEnumerable(ref r1, Width, 1); @@ -53,7 +54,7 @@ public RefEnumerable GetRow(int row) /// The returned value shouldn't be used directly: use this extension in a loop. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public RefEnumerable GetColumn(int column) + public unsafe RefEnumerable GetColumn(int column) { if ((uint)column >= Width) { @@ -61,7 +62,7 @@ public RefEnumerable GetColumn(int column) } ref T r0 = ref this.DangerousGetReference(); - ref T r1 = ref Unsafe.Add(ref r0, column); + ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)column); #if SPAN_RUNTIME_SUPPORT return new RefEnumerable(ref r1, Height, this.width + this.Pitch); @@ -188,7 +189,7 @@ public bool MoveNext() /// /// Gets the duck-typed property. /// - public ref T Current + public readonly unsafe ref T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -200,7 +201,7 @@ public ref T Current #endif int index = (this.y * (this.width + this.pitch)) + this.x; - return ref Unsafe.Add(ref r0, index); + return ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 33f7bed77e2..2b9567964df 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -494,7 +494,7 @@ public int Width /// /// Thrown when either or are invalid. /// - public ref T this[int i, int j] + public unsafe ref T this[int i, int j] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -512,7 +512,7 @@ public int Width #endif int index = (i * (this.width + this.Pitch)) + j; - return ref Unsafe.Add(ref r0, index); + return ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); } } @@ -737,7 +737,7 @@ public ref T DangerousGetReference() /// A reference to the element at the specified indices. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T DangerousGetReferenceAt(int i, int j) + public unsafe ref T DangerousGetReferenceAt(int i, int j) { #if SPAN_RUNTIME_SUPPORT ref T r0 = ref MemoryMarshal.GetReference(this.span); @@ -746,7 +746,7 @@ public ref T DangerousGetReferenceAt(int i, int j) #endif int index = (i * (this.width + this.Pitch)) + j; - return ref Unsafe.Add(ref r0, index); + return ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); } /// @@ -789,9 +789,12 @@ public Span2D Slice(int row, int column, int height, int width) pitch = this.Pitch + (this.width - width); #if SPAN_RUNTIME_SUPPORT - ref T r0 = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.span), shift); + unsafe + { + ref T r0 = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.span), (IntPtr)(void*)(uint)shift); - return new Span2D(ref r0, height, width, pitch); + return new Span2D(ref r0, height, width, pitch); + } #else IntPtr offset = this.Offset + (shift * Unsafe.SizeOf()); @@ -807,7 +810,7 @@ public Span2D Slice(int row, int column, int height, int width) /// Throw when is out of range. /// The resulting row . [Pure] - public Span GetRowSpan(int row) + public unsafe Span GetRowSpan(int row) { if ((uint)row >= (uint)Height) { @@ -816,7 +819,7 @@ public Span GetRowSpan(int row) int offset = (this.width + this.Pitch) * row; ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T r1 = ref Unsafe.Add(ref r0, offset); + ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); return MemoryMarshal.CreateSpan(ref r1, this.width); } From 1bd8e94b2d77719334983979bc68d917f96ca20e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 15 Aug 2020 17:13:56 +0200 Subject: [PATCH 101/200] Speed improvements to Span2D CopyTo/Fill/Clear methods Speedups related to runtimes without Span support (eg. UWP) --- .../Memory/Span2D{T}.cs | 132 +++++++++++++----- 1 file changed, 95 insertions(+), 37 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 2b9567964df..52f613264cb 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -448,7 +448,7 @@ public Span2D(T[,,] array, int depth, int row, int column, int height, int width public bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (Height | Width) == 0; + get => (Height | this.width) == 0; } /// @@ -457,7 +457,7 @@ public bool IsEmpty public int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Height * Width; + get => Height * this.width; } /// @@ -500,7 +500,7 @@ public int Width get { if ((uint)i >= (uint)Height || - (uint)j >= (uint)Width) + (uint)j >= (uint)this.width) { ThrowHelper.ThrowIndexOutOfRangeException(); } @@ -521,23 +521,38 @@ public int Width /// public void Clear() { + if (IsEmpty) + { + return; + } + if (TryGetSpan(out Span span)) { span.Clear(); } else { -#if SPAN_RUNTIME_SUPPORT // Clear one row at a time +#if SPAN_RUNTIME_SUPPORT for (int i = 0; i < Height; i++) { GetRowSpan(i).Clear(); } #else - // Fallback to the enumerator - foreach (ref T item in this) + unsafe { - item = default!; + int height = Height; + IntPtr width = (IntPtr)(void*)(uint)this.width; + + for (int i = 0; i < height; i++) + { + ref T r0 = ref DangerousGetReferenceAt(i, 0); + + for (IntPtr j = default; (void*)j < (void*)width; j += 1) + { + Unsafe.Add(ref r0, j) = default!; + } + } } #endif } @@ -552,6 +567,11 @@ public void Clear() /// public void CopyTo(Span destination) { + if (IsEmpty) + { + return; + } + if (TryGetSpan(out Span span)) { span.CopyTo(destination); @@ -563,22 +583,30 @@ public void CopyTo(Span destination) ThrowHelper.ThrowArgumentExceptionForDestinationTooShort(); } -#if SPAN_RUNTIME_SUPPORT // Copy each row individually - for (int i = 0, j = 0; i < Height; i++, j += Width) +#if SPAN_RUNTIME_SUPPORT + for (int i = 0, j = 0; i < Height; i++, j += this.width) { GetRowSpan(i).CopyTo(destination.Slice(j)); } #else - ref T destinationRef = ref MemoryMarshal.GetReference(destination); - IntPtr offset = default; - - // Fallback to the enumerator again - foreach (T item in this) + unsafe { - Unsafe.Add(ref destinationRef, offset) = item; + int height = Height; + IntPtr width = (IntPtr)(void*)(uint)this.width; + + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + IntPtr offset = default; - offset += 1; + for (int i = 0; i < height; i++) + { + ref T sourceRef = ref DangerousGetReferenceAt(i, 0); + + for (IntPtr j = default; (void*)j < (void*)width; j += 1, offset += 1) + { + Unsafe.Add(ref destinationRef, offset) = Unsafe.Add(ref sourceRef, j); + } + } } #endif } @@ -595,32 +623,44 @@ public void CopyTo(Span destination) public void CopyTo(Span2D destination) { if (destination.Height != Height || - destination.width != Width) + destination.width != this.width) { ThrowHelper.ThrowArgumentException(); } + if (IsEmpty) + { + return; + } + if (destination.TryGetSpan(out Span span)) { CopyTo(span); } else { -#if SPAN_RUNTIME_SUPPORT // Copy each row individually +#if SPAN_RUNTIME_SUPPORT for (int i = 0; i < Height; i++) { GetRowSpan(i).CopyTo(destination.GetRowSpan(i)); } #else - Enumerator destinationEnumerator = destination.GetEnumerator(); - - // Fallback path with two enumerators - foreach (T item in this) + unsafe { - _ = destinationEnumerator.MoveNext(); - - destinationEnumerator.Current = item; + int height = Height; + IntPtr width = (IntPtr)(void*)(uint)this.width; + + for (int i = 0; i < height; i++) + { + ref T sourceRef = ref DangerousGetReferenceAt(i, 0); + ref T destinationRef = ref destination.DangerousGetReferenceAt(i, 0); + + for (IntPtr j = default; (void*)j < (void*)width; j += 1) + { + Unsafe.Add(ref destinationRef, j) = Unsafe.Add(ref sourceRef, j); + } + } } #endif } @@ -651,7 +691,7 @@ public bool TryCopyTo(Span destination) public bool TryCopyTo(Span2D destination) { if (destination.Height == Height && - destination.Width == Width) + destination.Width == this.width) { CopyTo(destination); @@ -673,17 +713,27 @@ public void Fill(T value) } else { -#if SPAN_RUNTIME_SUPPORT // Fill one row at a time +#if SPAN_RUNTIME_SUPPORT for (int i = 0; i < Height; i++) { GetRowSpan(i).Fill(value); } #else - // Fill using the enumerator as above - foreach (ref T item in this) + unsafe { - item = value; + int height = Height; + IntPtr width = (IntPtr)(void*)(uint)this.width; + + for (int i = 0; i < height; i++) + { + ref T r0 = ref DangerousGetReferenceAt(i, 0); + + for (IntPtr j = default; (void*)j < (void*)width; j += 1) + { + Unsafe.Add(ref r0, j) = value; + } + } } #endif } @@ -878,15 +928,23 @@ public bool TryGetSpan(out Span span) // Skip the initialization if the array is empty if (Size > 0) { - ref T r0 = ref array.DangerousGetReference(); - IntPtr offset = default; - - // Fallback once again on the enumerator to copy the items - foreach (T item in this) + unsafe { - Unsafe.Add(ref r0, offset) = item; + int height = Height; + IntPtr width = (IntPtr)(void*)(uint)this.width; + + ref T destinationRef = ref array.DangerousGetReference(); + IntPtr offset = default; + + for (int i = 0; i < height; i++) + { + ref T sourceRef = ref DangerousGetReferenceAt(i, 0); - offset += 1; + for (IntPtr j = default; (void*)j < (void*)width; j += 1, offset += 1) + { + Unsafe.Add(ref destinationRef, offset) = Unsafe.Add(ref sourceRef, j); + } + } } } #endif From 4f145d6c55e24634c307dbd11d19c39e2409b9a5 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 16 Aug 2020 02:00:58 +0200 Subject: [PATCH 102/200] Ported speed improvements to ReadOnlySpan2D --- .../Memory/ReadOnlySpan2D{T}.cs | 81 +++++++++++++------ .../Memory/Span2D{T}.cs | 5 ++ 2 files changed, 62 insertions(+), 24 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index d142156ce3e..c42943981ac 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -496,6 +496,11 @@ public int Width /// public void CopyTo(Span destination) { + if (IsEmpty) + { + return; + } + if (TryGetSpan(out ReadOnlySpan span)) { span.CopyTo(destination); @@ -507,22 +512,30 @@ public void CopyTo(Span destination) ThrowHelper.ThrowArgumentExceptionForDestinationTooShort(); } -#if SPAN_RUNTIME_SUPPORT // Copy each row individually - for (int i = 0, j = 0; i < Height; i++, j += Width) +#if SPAN_RUNTIME_SUPPORT + for (int i = 0, j = 0; i < Height; i++, j += this.width) { GetRowSpan(i).CopyTo(destination.Slice(j)); } #else - ref T destinationRef = ref MemoryMarshal.GetReference(destination); - IntPtr offset = default; - - // Fallback to the enumerator again - foreach (T item in this) + unsafe { - Unsafe.Add(ref destinationRef, offset) = item; + int height = Height; + IntPtr width = (IntPtr)(void*)(uint)this.width; + + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + IntPtr offset = default; - offset += 1; + for (int i = 0; i < height; i++) + { + ref T sourceRef = ref DangerousGetReferenceAt(i, 0); + + for (IntPtr j = default; (void*)j < (void*)width; j += 1, offset += 1) + { + Unsafe.Add(ref destinationRef, offset) = Unsafe.Add(ref sourceRef, j); + } + } } #endif } @@ -544,27 +557,39 @@ public void CopyTo(Span2D destination) ThrowHelper.ThrowArgumentException(); } + if (IsEmpty) + { + return; + } + if (destination.TryGetSpan(out Span span)) { CopyTo(span); } else { -#if SPAN_RUNTIME_SUPPORT // Copy each row individually +#if SPAN_RUNTIME_SUPPORT for (int i = 0; i < Height; i++) { GetRowSpan(i).CopyTo(destination.GetRowSpan(i)); } #else - Span2D.Enumerator destinationEnumerator = destination.GetEnumerator(); - - // Fallback path with two enumerators - foreach (T item in this) + unsafe { - _ = destinationEnumerator.MoveNext(); - - destinationEnumerator.Current = item; + int height = Height; + IntPtr width = (IntPtr)(void*)(uint)this.width; + + for (int i = 0; i < height; i++) + { + ref T sourceRef = ref DangerousGetReferenceAt(i, 0); + ref T destinationRef = ref destination.DangerousGetReferenceAt(i, 0); + + for (IntPtr j = default; (void*)j < (void*)width; j += 1) + { + Unsafe.Add(ref destinationRef, j) = Unsafe.Add(ref sourceRef, j); + } + } } #endif } @@ -794,15 +819,23 @@ public bool TryGetSpan(out ReadOnlySpan span) // Skip the initialization if the array is empty if (Size > 0) { - ref T r0 = ref array.DangerousGetReference(); - IntPtr offset = default; - - // Fallback once again on the enumerator to copy the items - foreach (T item in this) + unsafe { - Unsafe.Add(ref r0, offset) = item; + int height = Height; + IntPtr width = (IntPtr)(void*)(uint)this.width; + + ref T destinationRef = ref array.DangerousGetReference(); + IntPtr offset = default; + + for (int i = 0; i < height; i++) + { + ref T sourceRef = ref DangerousGetReferenceAt(i, 0); - offset += 1; + for (IntPtr j = default; (void*)j < (void*)width; j += 1, offset += 1) + { + Unsafe.Add(ref destinationRef, offset) = Unsafe.Add(ref sourceRef, j); + } + } } } #endif diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 52f613264cb..804beedd4b4 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -707,6 +707,11 @@ public bool TryCopyTo(Span2D destination) /// The value to assign to each element of the instance. public void Fill(T value) { + if (IsEmpty) + { + return; + } + if (TryGetSpan(out Span span)) { span.Fill(value); From c818eb2282a8ee128573c1c552a7f2e1dc2dff04 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 16 Aug 2020 12:41:31 +0200 Subject: [PATCH 103/200] Improved handling of Memory(string) inputs for Memory2D --- .../Memory/Memory2D{T}.cs | 23 ++++++++++++++----- .../Memory/ReadOnlyMemory2D{T}.cs | 13 +++++++++-- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index c6b23fead59..8533d93d489 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -369,13 +369,24 @@ public Memory2D(Memory memory, int offset, int height, int width, int pitch) ThrowHelper.ThrowArgumentException(); } - // Check if the input Memory instance wraps an array we can access. - // This is fine, since Memory on its own doesn't control the lifetime - // of the underlying array anyway, and this Memory2D type would do the same. - // Using the array directly makes retrieving a Span2D faster down the line, - // as we no longer have to jump through the boxed Memory first anymore. - if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) + // Check if the Memory instance wraps a string. This is possible in case + // consumers do an unsafe cast for the entire Memory object, and while not + // really safe it is still supported in CoreCLR too, so we're following suit here. + if (typeof(T) == typeof(char) && + MemoryMarshal.TryGetString(Unsafe.As, Memory>(ref memory), out string? text, out int start, out _)) { + ref char r0 = ref text.DangerousGetReferenceAt(start + offset); + + this.instance = text; + this.offset = text.DangerousGetObjectDataByteOffset(ref r0); + } + else if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) + { + // Check if the input Memory instance wraps an array we can access. + // This is fine, since Memory on its own doesn't control the lifetime + // of the underlying array anyway, and this Memory2D type would do the same. + // Using the array directly makes retrieving a Span2D faster down the line, + // as we no longer have to jump through the boxed Memory first anymore. T[] array = segment.Array!; this.instance = array; diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index be16357e458..853297c85d9 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -440,9 +440,18 @@ public ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int height, int wi ThrowHelper.ThrowArgumentException(); } - // Access the array directly, if possible, just like in Memory2D - if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) + // Check whether the memory wraps a string we can directly access + if (typeof(T) == typeof(char) && + MemoryMarshal.TryGetString(Unsafe.As, ReadOnlyMemory>(ref memory), out string? text, out int start, out _)) { + ref char r0 = ref text.DangerousGetReferenceAt(start + offset); + + this.instance = text; + this.offset = text.DangerousGetObjectDataByteOffset(ref r0); + } + else if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) + { + // Access the array directly, if possible, just like in Memory2D T[] array = segment.Array!; this.instance = array; From f9fa7a3a1e60d371c8db45d21e860ff71da4359b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 16 Aug 2020 13:18:36 +0200 Subject: [PATCH 104/200] Fixed two typos --- Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs | 2 +- Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 8533d93d489..bab871d1eac 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -486,7 +486,7 @@ public bool IsEmpty } /// - /// Gets the length of the current instance. + /// Gets the length of the current instance. /// public int Size { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 853297c85d9..023436645c1 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -551,7 +551,7 @@ public bool IsEmpty } /// - /// Gets the length of the current instance. + /// Gets the length of the current instance. /// public int Size { From 5be4959e51fbea7f8bde8e0a1c3ea24564a55cf9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 18 Aug 2020 01:27:55 +0200 Subject: [PATCH 105/200] Minor code refactoring --- .../Memory/ReadOnlySpan2D{T}.cs | 62 +++++++++++++------ .../Memory/Span2D{T}.cs | 62 +++++++++++++------ 2 files changed, 84 insertions(+), 40 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index c42943981ac..f2076ddcfd7 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -62,24 +62,9 @@ public readonly ref partial struct ReadOnlySpan2D /// The height of the 2D memory area to map. /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map (the distance between each row). - /// Thrown when one of the parameters are negative. - public ReadOnlySpan2D(in T value, int height, int width, int pitch) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan2D(in T value, int height, int width, int pitch) { - if (width < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); - } - - if (height < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); - } - - if (pitch < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); - } - this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(value), height); this.width = width; this.pitch = pitch; @@ -119,24 +104,29 @@ public unsafe ReadOnlySpan2D(void* pointer, int height, int width, int pitch) this.width = width; this.pitch = pitch; } -#else +#endif + /// /// Initializes a new instance of the struct with the specified parameters. /// /// The target instance. - /// The initial offset within . + /// The initial offset within the target instance. /// The height of the 2D memory area to map. /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map. + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ReadOnlySpan2D(object instance, IntPtr offset, int height, int width, int pitch) { +#if SPAN_RUNTIME_SUPPORT + this.span = MemoryMarshal.CreateReadOnlySpan(ref instance.DangerousGetObjectDataReferenceAt(offset), height); +#else this.instance = instance; this.offset = offset; this.height = height; +#endif this.width = width; this.pitch = pitch; } -#endif /// /// Initializes a new instance of the struct. @@ -408,6 +398,38 @@ public ReadOnlySpan2D(T[,,] array, int depth, int row, int column, int height, i this.pitch = columns - width; } +#if SPAN_RUNTIME_SUPPORT + /// + /// Creates a new instance of the struct with the specified parameters. + /// + /// The reference to the first item to map. + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map (the distance between each row). + /// A instance with the specified parameters. + /// Thrown when one of the parameters are negative. + [Pure] + public static ReadOnlySpan2D DangerousCreate(in T value, int height, int width, int pitch) + { + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + return new ReadOnlySpan2D(value, height, width, pitch); + } +#endif + /// /// Gets an empty instance. /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 804beedd4b4..7d74fa7b64d 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -91,24 +91,9 @@ public readonly ref partial struct Span2D /// The height of the 2D memory area to map. /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map (the distance between each row). - /// Thrown when one of the parameters are negative. - public Span2D(ref T value, int height, int width, int pitch) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Span2D(ref T value, int height, int width, int pitch) { - if (width < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); - } - - if (height < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); - } - - if (pitch < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); - } - this.span = MemoryMarshal.CreateSpan(ref value, height); this.width = width; this.Pitch = pitch; @@ -148,24 +133,29 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) this.width = width; this.Pitch = pitch; } -#else +#endif + /// /// Initializes a new instance of the struct with the specified parameters. /// /// The target instance. - /// The initial offset within . + /// The initial offset within the target instance. /// The height of the 2D memory area to map. /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map. + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Span2D(object instance, IntPtr offset, int height, int width, int pitch) { +#if SPAN_RUNTIME_SUPPORT + this.span = MemoryMarshal.CreateSpan(ref instance.DangerousGetObjectDataReferenceAt(offset), height); +#else this.Instance = instance; this.Offset = offset; this.height = height; +#endif this.width = width; this.Pitch = pitch; } -#endif /// /// Initializes a new instance of the struct. @@ -437,6 +427,38 @@ public Span2D(T[,,] array, int depth, int row, int column, int height, int width this.Pitch = columns - width; } +#if SPAN_RUNTIME_SUPPORT + /// + /// Creates a new instance of the struct with the specified parameters. + /// + /// The reference to the first item to map. + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map (the distance between each row). + /// A instance with the specified parameters. + /// Thrown when one of the parameters are negative. + [Pure] + public static Span2D DangerousCreate(ref T value, int height, int width, int pitch) + { + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + return new Span2D(ref value, height, width, pitch); + } +#endif + /// /// Gets an empty instance. /// From b5f728fd5b8bd4a5cc4c0e3124781846a7757346 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 18 Aug 2020 13:49:33 +0200 Subject: [PATCH 106/200] Fixed build errors in the unit tests (refactoring) --- .../Memory/Test_ReadOnlySpan2D{T}.cs | 8 ++++---- .../Memory/Test_Span2D{T}.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs index c158088e378..ffe01b93caa 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs @@ -45,7 +45,7 @@ public unsafe void Test_ReadOnlySpan2DT_RefConstructor() 1, 2, 3, 4, 5, 6 }; - ReadOnlySpan2D span2d = new ReadOnlySpan2D(span[0], 2, 3, 0); + ReadOnlySpan2D span2d = ReadOnlySpan2D.DangerousCreate(span[0], 2, 3, 0); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(span2d.Size, 6); @@ -54,9 +54,9 @@ public unsafe void Test_ReadOnlySpan2DT_RefConstructor() Assert.AreEqual(span2d[0, 0], 1); Assert.AreEqual(span2d[1, 2], 6); - Assert.ThrowsException(() => new ReadOnlySpan2D(Unsafe.AsRef(null), -1, 0, 0)); - Assert.ThrowsException(() => new ReadOnlySpan2D(Unsafe.AsRef(null), 1, -2, 0)); - Assert.ThrowsException(() => new ReadOnlySpan2D(Unsafe.AsRef(null), 1, 0, -5)); + Assert.ThrowsException(() => ReadOnlySpan2D.DangerousCreate(Unsafe.AsRef(null), -1, 0, 0)); + Assert.ThrowsException(() => ReadOnlySpan2D.DangerousCreate(Unsafe.AsRef(null), 1, -2, 0)); + Assert.ThrowsException(() => ReadOnlySpan2D.DangerousCreate(Unsafe.AsRef(null), 1, 0, -5)); } [TestCategory("ReadOnlySpan2DT")] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs index ee5cf93e5d8..ffd50b9260c 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -45,7 +45,7 @@ public unsafe void Test_Span2DT_RefConstructor() 1, 2, 3, 4, 5, 6 }; - Span2D span2d = new Span2D(ref span[0], 2, 3, 0); + Span2D span2d = Span2D.DangerousCreate(ref span[0], 2, 3, 0); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(span2d.Size, 6); @@ -58,9 +58,9 @@ public unsafe void Test_Span2DT_RefConstructor() Assert.AreEqual(span[0], 99); Assert.AreEqual(span[5], 101); - Assert.ThrowsException(() => new Span2D(ref Unsafe.AsRef(null), -1, 0, 0)); - Assert.ThrowsException(() => new Span2D(ref Unsafe.AsRef(null), 1, -2, 0)); - Assert.ThrowsException(() => new Span2D(ref Unsafe.AsRef(null), 1, 0, -5)); + Assert.ThrowsException(() => Span2D.DangerousCreate(ref Unsafe.AsRef(null), -1, 0, 0)); + Assert.ThrowsException(() => Span2D.DangerousCreate(ref Unsafe.AsRef(null), 1, -2, 0)); + Assert.ThrowsException(() => Span2D.DangerousCreate(ref Unsafe.AsRef(null), 1, 0, -5)); } [TestCategory("Span2DT")] From 5ed38ff70cae96d3ec6ac4c91e3d046145b33b78 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 19 Aug 2020 10:36:48 +0200 Subject: [PATCH 107/200] Added missing nullability annotation --- .../Extensions/ArrayExtensions.2D.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 11db02d4bed..4e6c2ba8e9d 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -253,7 +253,7 @@ public static RefEnumerable GetColumn(this T[,] array, int column) /// A instance with the values of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span2D AsSpan2D(this T[,] array) + public static Span2D AsSpan2D(this T[,]? array) { return new Span2D(array); } From 61094769aa7ce58f46ca1935b9d6e0ff197b60e1 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 24 Aug 2020 13:36:22 +0200 Subject: [PATCH 108/200] Extended [ReadOnly]Span2D(void*) support Now available on .NET Standard <= 2.0 too, like Span --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 21 +-- .../Enumerables/RefEnumerable{T}.cs | 21 +-- .../Helpers/Internals/RuntimeHelpers.cs | 145 ++++++++++++++++++ .../Memory/Internals/ThrowHelper.cs | 4 +- .../Memory/ReadOnlySpan2D{T}.Enumerator.cs | 12 +- .../Memory/ReadOnlySpan2D{T}.cs | 45 ++++-- .../Memory/Span2D{T}.Enumerator.cs | 16 +- .../Memory/Span2D{T}.cs | 43 ++++-- .../Memory/Test_ReadOnlySpan2D{T}.cs | 11 +- .../Memory/Test_Span2D{T}.cs | 10 +- 10 files changed, 257 insertions(+), 71 deletions(-) create mode 100644 Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index 14b716fd5f4..8dbcbede1e1 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -10,6 +10,9 @@ using System.Runtime.InteropServices; #endif using Microsoft.Toolkit.HighPerformance.Extensions; +#if !SPAN_RUNTIME_SUPPORT +using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; +#endif namespace Microsoft.Toolkit.HighPerformance.Enumerables { @@ -20,12 +23,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables [EditorBrowsable(EditorBrowsableState.Never)] public ref struct ReadOnlyRefEnumerable { - /// - /// The distance between items in the sequence to enumerate. - /// - /// The distance refers to items, not byte offset. - private readonly int step; - #if SPAN_RUNTIME_SUPPORT /// /// The instance pointing to the first item in the target memory area. @@ -49,6 +46,12 @@ public ref struct ReadOnlyRefEnumerable private readonly int length; #endif + /// + /// The distance between items in the sequence to enumerate. + /// + /// The distance refers to items, not byte offset. + private readonly int step; + /// /// The current position in the sequence. /// @@ -77,7 +80,7 @@ internal ReadOnlyRefEnumerable(in T reference, int length, int step) /// The number of items in the sequence. /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlyRefEnumerable(object instance, IntPtr offset, int length, int step) + internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int step) { this.instance = instance; this.offset = offset; @@ -125,7 +128,7 @@ public readonly ref readonly T Current #else unsafe { - ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.position); return ref ri; @@ -159,7 +162,7 @@ public readonly unsafe T[] ToArray() #if SPAN_RUNTIME_SUPPORT ref T sourceRef = ref this.span.DangerousGetReference(); #else - ref T sourceRef = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif T[] array = new T[length / this.step]; diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 62228cd16db..2d8fec96359 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -10,6 +10,9 @@ using System.Runtime.InteropServices; #endif using Microsoft.Toolkit.HighPerformance.Extensions; +#if !SPAN_RUNTIME_SUPPORT +using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; +#endif namespace Microsoft.Toolkit.HighPerformance.Enumerables { @@ -20,12 +23,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables [EditorBrowsable(EditorBrowsableState.Never)] public ref struct RefEnumerable { - /// - /// The distance between items in the sequence to enumerate. - /// - /// The distance refers to items, not byte offset. - private readonly int step; - #if SPAN_RUNTIME_SUPPORT /// /// The instance pointing to the first item in the target memory area. @@ -49,6 +46,12 @@ public ref struct RefEnumerable private readonly int length; #endif + /// + /// The distance between items in the sequence to enumerate. + /// + /// The distance refers to items, not byte offset. + private readonly int step; + /// /// The current position in the sequence. /// @@ -77,7 +80,7 @@ internal RefEnumerable(ref T reference, int length, int step) /// The number of items in the sequence. /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal RefEnumerable(object instance, IntPtr offset, int length, int step) + internal RefEnumerable(object? instance, IntPtr offset, int length, int step) { this.instance = instance; this.offset = offset; @@ -125,7 +128,7 @@ public readonly ref T Current #else unsafe { - ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.position); return ref ri; @@ -168,7 +171,7 @@ public readonly unsafe T[] ToArray() #if SPAN_RUNTIME_SUPPORT ref T sourceRef = ref this.span.DangerousGetReference(); #else - ref T sourceRef = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif T[] array = new T[length / this.step]; diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs new file mode 100644 index 00000000000..1b0bda97b30 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs @@ -0,0 +1,145 @@ +// 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. + +#pragma warning disable SA1512 + +// The portable implementation in this type is originally from CoreFX. +// See https://github.com/dotnet/corefx/blob/release/2.1/src/System.Memory/src/System/SpanHelpers.cs. + +#if !SPAN_RUNTIME_SUPPORT + +using System; +using System.Diagnostics.Contracts; +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.Toolkit.HighPerformance.Extensions; + +namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals +{ + /// + /// A helper class that act as polyfill for .NET Standard 2.0 and below. + /// + internal static class RuntimeHelpers + { + /// + /// Gets a byte offset describing a portable pinnable reference. This can either be an + /// interior pointer into some object data (described with a valid reference + /// and a reference to some of its data), or a raw pointer (described with a + /// reference to an , and a reference that is assumed to refer to pinned data). + /// + /// The type of field being referenced. + /// The input hosting the target field. + /// A reference to a target field of type within . + /// + /// The value representing the offset to the target field from the start of the object data + /// for the parameter , or the value of the raw pointer passed as a tracked reference. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe IntPtr GetObjectDataOrReferenceByteOffset(object? obj, ref T data) + { + if (obj is null) + { + return (IntPtr)Unsafe.AsPointer(ref data); + } + + return obj.DangerousGetObjectDataByteOffset(ref data); + } + + /// + /// Gets a reference from data describing a portable pinnable reference. This can either be an + /// interior pointer into some object data (described with a valid reference + /// and a byte offset into its data), or a raw pointer (described with a + /// reference to an , and a byte offset representing the value of the raw pointer). + /// + /// The type of reference to retrieve. + /// The input hosting the target field. + /// The input byte offset for the reference to retrieve. + /// A reference matching the given parameters. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref T GetObjectDataAtOffsetOrPointerReference(object? obj, IntPtr offset) + { + if (obj is null) + { + return ref Unsafe.AsRef((void*)offset); + } + + return ref obj.DangerousGetObjectDataReferenceAt(offset); + } + + /// + /// Checks whether or not a given type is a reference type or contains references. + /// + /// The type to check. + /// Whether or not respects the constraint. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsReferenceOrContainsReferences() + { + return TypeInfo.IsReferenceOrContainsReferences; + } + + /// + /// Implements the logic for . + /// + /// The current type to check. + /// Whether or not is a reference type or contains references. + [Pure] + private static bool IsReferenceOrContainsReferences(Type type) + { + // Common case, for primitive types + if (type.GetTypeInfo().IsPrimitive) + { + return false; + } + + if (!type.GetTypeInfo().IsValueType) + { + return true; + } + + // Check if the type is Nullable + if (Nullable.GetUnderlyingType(type) is Type nullableType) + { + type = nullableType; + } + + if (type.GetTypeInfo().IsEnum) + { + return false; + } + + // Complex struct, recursively inspect all fields + foreach (FieldInfo field in type.GetTypeInfo().DeclaredFields) + { + if (field.IsStatic) + { + continue; + } + + if (IsReferenceOrContainsReferences(field.FieldType)) + { + return true; + } + } + + return false; + } + + /// + /// A private generic class to preload type info for arbitrary runtime types. + /// + /// The type to load info for. + private static class TypeInfo + { + /// + /// Indicates whether does not respect the constraint. + /// + public static readonly bool IsReferenceOrContainsReferences = IsReferenceOrContainsReferences(typeof(T)); + } + } +} + +#endif \ No newline at end of file diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs b/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs index 70b5f20142b..72dbac3899c 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs @@ -11,15 +11,13 @@ namespace Microsoft.Toolkit.HighPerformance.Memory.Internals /// internal static class ThrowHelper { -#if SPAN_RUNTIME_SUPPORT /// /// Throws an when using the * constructor with a managed type. /// public static void ThrowArgumentExceptionForManagedType() { - throw new ArgumentException("Can't create a Span2D from a pointer when T is a managed type"); + throw new ArgumentException("Can't use a void* constructor when T is a managed type"); } -#endif /// /// Throws an when the target span is too short. diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs index 67b1b75fef2..14f472ef3c4 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs @@ -10,7 +10,7 @@ #if SPAN_RUNTIME_SUPPORT using System.Runtime.InteropServices; #else -using Microsoft.Toolkit.HighPerformance.Extensions; +using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; #endif namespace Microsoft.Toolkit.HighPerformance.Memory @@ -34,13 +34,13 @@ public unsafe ReadOnlyRefEnumerable GetRow(int row) } int startIndex = (this.width + this.pitch) * row; - ref T r0 = ref this.DangerousGetReference(); + ref T r0 = ref DangerousGetReference(); ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)startIndex); #if SPAN_RUNTIME_SUPPORT return new ReadOnlyRefEnumerable(r1, Width, 1); #else - IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); + IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1); return new ReadOnlyRefEnumerable(this.instance!, offset, this.width, 1); #endif @@ -61,13 +61,13 @@ public unsafe ReadOnlyRefEnumerable GetColumn(int column) ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } - ref T r0 = ref this.DangerousGetReference(); + ref T r0 = ref DangerousGetReference(); ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)column); #if SPAN_RUNTIME_SUPPORT return new ReadOnlyRefEnumerable(r1, Height, this.width + this.pitch); #else - IntPtr offset = this.instance!.DangerousGetObjectDataByteOffset(ref r1); + IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1); return new ReadOnlyRefEnumerable(this.instance!, offset, Height, this.width + this.pitch); #endif @@ -197,7 +197,7 @@ public readonly unsafe ref readonly T Current #if SPAN_RUNTIME_SUPPORT ref T r0 = ref MemoryMarshal.GetReference(this.span); #else - ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif int index = (this.y * (this.width + this.pitch)) + this.x; diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index f2076ddcfd7..a88e0933c5b 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -11,6 +11,9 @@ using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory.Internals; using Microsoft.Toolkit.HighPerformance.Memory.Views; +#if !SPAN_RUNTIME_SUPPORT +using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; +#endif namespace Microsoft.Toolkit.HighPerformance.Memory { @@ -69,6 +72,7 @@ internal ReadOnlySpan2D(in T value, int height, int width, int pitch) this.width = width; this.pitch = pitch; } +#endif /// /// Initializes a new instance of the struct with the specified parameters. @@ -100,11 +104,16 @@ public unsafe ReadOnlySpan2D(void* pointer, int height, int width, int pitch) ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } - this.span = new Span(pointer, height); +#if SPAN_RUNTIME_SUPPORT + this.span = new ReadOnlySpan(pointer, height); +#else + this.instance = null; + this.offset = (IntPtr)pointer; + this.height = height; +#endif this.width = width; this.pitch = pitch; } -#endif /// /// Initializes a new instance of the struct with the specified parameters. @@ -487,7 +496,7 @@ public int Width /// /// Thrown when either or are invalid. /// - public unsafe ref readonly T this[int i, int j] + public ref readonly T this[int i, int j] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -498,14 +507,7 @@ public int Width ThrowHelper.ThrowIndexOutOfRangeException(); } -#if SPAN_RUNTIME_SUPPORT - ref T r0 = ref MemoryMarshal.GetReference(this.span); -#else - ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); -#endif - int index = (i * (this.width + this.pitch)) + j; - - return ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); + return ref DangerousGetReferenceAt(i, j); } } @@ -670,7 +672,7 @@ public unsafe ref T GetPinnableReference() #if SPAN_RUNTIME_SUPPORT r0 = ref MemoryMarshal.GetReference(this.span); #else - r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif } @@ -688,7 +690,7 @@ public ref T DangerousGetReference() #if SPAN_RUNTIME_SUPPORT return ref MemoryMarshal.GetReference(this.span); #else - return ref this.GetPinnableReference(); + return ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif } @@ -705,7 +707,7 @@ public unsafe ref T DangerousGetReferenceAt(int i, int j) #if SPAN_RUNTIME_SUPPORT ref T r0 = ref MemoryMarshal.GetReference(this.span); #else - ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif int index = (i * (this.width + this.pitch)) + j; @@ -795,22 +797,33 @@ public unsafe ReadOnlySpan GetRowSpan(int row) /// Whether or not was correctly assigned. public bool TryGetSpan(out ReadOnlySpan span) { + // We can only create a Span if the buffer is contiguous if (this.pitch == 0) { #if SPAN_RUNTIME_SUPPORT - // We can only create a Span if the buffer is contiguous span = MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(this.span), Size); return true; #else // An empty Span2D is still valid - if (this.instance is null) + if (IsEmpty) { span = default; return true; } + // Pinned ReadOnlySpan2D + if (this.instance is null) + { + unsafe + { + span = new Span((void*)this.offset, Size); + } + + return true; + } + // Without Span runtime support, we can only get a Span from a T[] instance if (this.instance.GetType() == typeof(T[])) { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index 762788e8db9..6178674e4fd 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -10,7 +10,7 @@ #if SPAN_RUNTIME_SUPPORT using System.Runtime.InteropServices; #else -using Microsoft.Toolkit.HighPerformance.Extensions; +using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; #endif namespace Microsoft.Toolkit.HighPerformance.Memory @@ -34,15 +34,15 @@ public unsafe RefEnumerable GetRow(int row) } int startIndex = (this.width + this.Pitch) * row; - ref T r0 = ref this.DangerousGetReference(); + ref T r0 = ref DangerousGetReference(); ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)startIndex); #if SPAN_RUNTIME_SUPPORT return new RefEnumerable(ref r1, Width, 1); #else - IntPtr offset = this.Instance!.DangerousGetObjectDataByteOffset(ref r1); + IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.Instance, ref r1); - return new RefEnumerable(this.Instance!, offset, this.width, 1); + return new RefEnumerable(this.Instance, offset, this.width, 1); #endif } @@ -61,15 +61,15 @@ public unsafe RefEnumerable GetColumn(int column) ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn(); } - ref T r0 = ref this.DangerousGetReference(); + ref T r0 = ref DangerousGetReference(); ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)column); #if SPAN_RUNTIME_SUPPORT return new RefEnumerable(ref r1, Height, this.width + this.Pitch); #else - IntPtr offset = this.Instance!.DangerousGetObjectDataByteOffset(ref r1); + IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.Instance, ref r1); - return new RefEnumerable(this.Instance!, offset, Height, this.width + this.Pitch); + return new RefEnumerable(this.Instance, offset, Height, this.width + this.Pitch); #endif } @@ -197,7 +197,7 @@ public readonly unsafe ref T Current #if SPAN_RUNTIME_SUPPORT ref T r0 = ref MemoryMarshal.GetReference(this.span); #else - ref T r0 = ref this.instance!.DangerousGetObjectDataReferenceAt(this.offset); + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif int index = (this.y * (this.width + this.pitch)) + this.x; diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 7d74fa7b64d..187f473fec1 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -11,6 +11,9 @@ using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory.Internals; using Microsoft.Toolkit.HighPerformance.Memory.Views; +#if !SPAN_RUNTIME_SUPPORT +using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; +#endif namespace Microsoft.Toolkit.HighPerformance.Memory { @@ -98,6 +101,7 @@ internal Span2D(ref T value, int height, int width, int pitch) this.width = width; this.Pitch = pitch; } +#endif /// /// Initializes a new instance of the struct with the specified parameters. @@ -129,11 +133,16 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } +#if SPAN_RUNTIME_SUPPORT this.span = new Span(pointer, height); +#else + this.Instance = null; + this.Offset = (IntPtr)pointer; + this.height = height; +#endif this.width = width; this.Pitch = pitch; } -#endif /// /// Initializes a new instance of the struct with the specified parameters. @@ -516,7 +525,7 @@ public int Width /// /// Thrown when either or are invalid. /// - public unsafe ref T this[int i, int j] + public ref T this[int i, int j] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -527,14 +536,7 @@ public int Width ThrowHelper.ThrowIndexOutOfRangeException(); } -#if SPAN_RUNTIME_SUPPORT - ref T r0 = ref MemoryMarshal.GetReference(this.span); -#else - ref T r0 = ref this.Instance!.DangerousGetObjectDataReferenceAt(this.Offset); -#endif - int index = (i * (this.width + this.Pitch)) + j; - - return ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); + return ref DangerousGetReferenceAt(i, j); } } @@ -784,7 +786,7 @@ public unsafe ref T GetPinnableReference() #if SPAN_RUNTIME_SUPPORT r0 = ref MemoryMarshal.GetReference(this.span); #else - r0 = ref this.Instance!.DangerousGetObjectDataReferenceAt(this.Offset); + r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); #endif } @@ -802,7 +804,7 @@ public ref T DangerousGetReference() #if SPAN_RUNTIME_SUPPORT return ref MemoryMarshal.GetReference(this.span); #else - return ref this.GetPinnableReference(); + return ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); #endif } @@ -819,7 +821,7 @@ public unsafe ref T DangerousGetReferenceAt(int i, int j) #if SPAN_RUNTIME_SUPPORT ref T r0 = ref MemoryMarshal.GetReference(this.span); #else - ref T r0 = ref this.Instance!.DangerousGetObjectDataReferenceAt(this.Offset); + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); #endif int index = (i * (this.width + this.Pitch)) + j; @@ -909,22 +911,33 @@ public unsafe Span GetRowSpan(int row) /// Whether or not was correctly assigned. public bool TryGetSpan(out Span span) { + // We can only create a Span if the buffer is contiguous if (this.Pitch == 0) { #if SPAN_RUNTIME_SUPPORT - // We can only create a Span if the buffer is contiguous span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Size); return true; #else // An empty Span2D is still valid - if (this.Instance is null) + if (IsEmpty) { span = default; return true; } + // Pinned Span2D + if (this.Instance is null) + { + unsafe + { + span = new Span((void*)this.Offset, Size); + } + + return true; + } + // Without Span runtime support, we can only get a Span from a T[] instance if (this.Instance.GetType() == typeof(T[])) { diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs index ffe01b93caa..98525b4aa06 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs @@ -6,7 +6,6 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Enumerables; -using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -58,6 +57,7 @@ public unsafe void Test_ReadOnlySpan2DT_RefConstructor() Assert.ThrowsException(() => ReadOnlySpan2D.DangerousCreate(Unsafe.AsRef(null), 1, -2, 0)); Assert.ThrowsException(() => ReadOnlySpan2D.DangerousCreate(Unsafe.AsRef(null), 1, 0, -5)); } +#endif [TestCategory("ReadOnlySpan2DT")] [TestMethod] @@ -65,7 +65,12 @@ public unsafe void Test_ReadOnlySpan2DT_PtrConstructor() { int* ptr = stackalloc[] { - 1, 2, 3, 4, 5, 6 + 1, + 2, + 3, + 4, + 5, + 6 }; ReadOnlySpan2D span2d = new ReadOnlySpan2D(ptr, 2, 3, 0); @@ -80,8 +85,8 @@ public unsafe void Test_ReadOnlySpan2DT_PtrConstructor() Assert.ThrowsException(() => new ReadOnlySpan2D((void*)0, -1, 0, 0)); Assert.ThrowsException(() => new ReadOnlySpan2D((void*)0, 1, -2, 0)); Assert.ThrowsException(() => new ReadOnlySpan2D((void*)0, 1, 0, -5)); + Assert.ThrowsException(() => new ReadOnlySpan2D((void*)0, 2, 2, 0)); } -#endif [TestCategory("ReadOnlySpan2DT")] [TestMethod] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs index ffd50b9260c..4a68877db51 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -62,6 +62,7 @@ public unsafe void Test_Span2DT_RefConstructor() Assert.ThrowsException(() => Span2D.DangerousCreate(ref Unsafe.AsRef(null), 1, -2, 0)); Assert.ThrowsException(() => Span2D.DangerousCreate(ref Unsafe.AsRef(null), 1, 0, -5)); } +#endif [TestCategory("Span2DT")] [TestMethod] @@ -69,7 +70,12 @@ public unsafe void Test_Span2DT_PtrConstructor() { int* ptr = stackalloc[] { - 1, 2, 3, 4, 5, 6 + 1, + 2, + 3, + 4, + 5, + 6 }; Span2D span2d = new Span2D(ptr, 2, 3, 0); @@ -88,8 +94,8 @@ public unsafe void Test_Span2DT_PtrConstructor() Assert.ThrowsException(() => new Span2D((void*)0, -1, 0, 0)); Assert.ThrowsException(() => new Span2D((void*)0, 1, -2, 0)); Assert.ThrowsException(() => new Span2D((void*)0, 1, 0, -5)); + Assert.ThrowsException(() => new Span2D((void*)0, 2, 2, 0)); } -#endif [TestCategory("Span2DT")] [TestMethod] From e0b9f8e6f2cd4feeed7ac3671f2debe057115599 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 24 Aug 2020 13:52:08 +0200 Subject: [PATCH 109/200] Added more unit tests for Span2D(void*) --- .../Memory/Test_ReadOnlySpan2D{T}.cs | 77 +++++++++++++++++++ .../Memory/Test_Span2D{T}.cs | 65 ++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs index 98525b4aa06..847df116e84 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs @@ -697,6 +697,33 @@ public void Test_ReadOnlySpan2DT_GetRow() Assert.ThrowsException(() => new ReadOnlySpan2D(array).GetRow(1000)); } + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public unsafe void Test_ReadOnlySpan2DT_Pointer_GetRow() + { + int* array = stackalloc[] + { + 1, 2, 3, + 4, 5, 6 + }; + + int i = 0; + foreach (ref readonly int value in new ReadOnlySpan2D(array, 2, 3, 0).GetRow(1)) + { + Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(value), ref array[3 + i++])); + } + + ReadOnlyRefEnumerable enumerable = new ReadOnlySpan2D(array, 2, 3, 0).GetRow(1); + + int[] expected = { 4, 5, 6 }; + + CollectionAssert.AreEqual(enumerable.ToArray(), expected); + + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 2, 3, 0).GetRow(-1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 2, 3, 0).GetRow(2)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 2, 3, 0).GetRow(1000)); + } + [TestCategory("ReadOnlySpan2DT")] [TestMethod] public void Test_ReadOnlySpan2DT_GetColumn() @@ -724,6 +751,33 @@ public void Test_ReadOnlySpan2DT_GetColumn() Assert.ThrowsException(() => new ReadOnlySpan2D(array).GetColumn(1000)); } + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public unsafe void Test_ReadOnlySpan2DT_Pointer_GetColumn() + { + int* array = stackalloc[] + { + 1, 2, 3, + 4, 5, 6 + }; + + int i = 0; + foreach (ref readonly int value in new ReadOnlySpan2D(array, 2, 3, 0).GetColumn(1)) + { + Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(value), ref array[(i++ * 3) + 1])); + } + + ReadOnlyRefEnumerable enumerable = new ReadOnlySpan2D(array, 2, 3, 0).GetColumn(2); + + int[] expected = { 3, 6 }; + + CollectionAssert.AreEqual(enumerable.ToArray(), expected); + + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 2, 3, 0).GetColumn(-1)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 2, 3, 0).GetColumn(3)); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, 2, 3, 0).GetColumn(1000)); + } + [TestCategory("ReadOnlySpan2DT")] [TestMethod] public void Test_ReadOnlySpan2DT_GetEnumerator() @@ -747,6 +801,29 @@ public void Test_ReadOnlySpan2DT_GetEnumerator() CollectionAssert.AreEqual(result, expected); } + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public unsafe void Test_ReadOnlySpan2DT_Pointer_GetEnumerator() + { + int* array = stackalloc[] + { + 1, 2, 3, + 4, 5, 6 + }; + + int[] result = new int[4]; + int i = 0; + + foreach (var item in new ReadOnlySpan2D(array + 1, 2, 2, 1)) + { + result[i++] = item; + } + + int[] expected = { 2, 3, 5, 6 }; + + CollectionAssert.AreEqual(result, expected); + } + [TestCategory("ReadOnlySpan2DT")] [TestMethod] public void Test_ReadOnlySpan2DT_GetEnumerator_Empty() diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs index 4a68877db51..a6825fc2344 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -774,6 +774,27 @@ public void Test_Span2DT_GetRow() Assert.ThrowsException(() => new Span2D(array).GetRow(1000)); } + [TestCategory("Span2DT")] + [TestMethod] + public unsafe void Test_Span2DT_Pointer_GetRow() + { + int* array = stackalloc[] + { + 1, 2, 3, + 4, 5, 6 + }; + + RefEnumerable enumerable = new Span2D(array, 2, 3, 0).GetRow(1); + + int[] expected = { 4, 5, 6 }; + + CollectionAssert.AreEqual(enumerable.ToArray(), expected); + + Assert.ThrowsException(() => new Span2D(array, 2, 3, 0).GetRow(-1)); + Assert.ThrowsException(() => new Span2D(array, 2, 3, 0).GetRow(2)); + Assert.ThrowsException(() => new Span2D(array, 2, 3, 0).GetRow(1000)); + } + [TestCategory("Span2DT")] [TestMethod] public void Test_Span2DT_GetColumn() @@ -795,6 +816,27 @@ public void Test_Span2DT_GetColumn() Assert.ThrowsException(() => new Span2D(array).GetColumn(1000)); } + [TestCategory("Span2DT")] + [TestMethod] + public unsafe void Test_Span2DT_Pointer_GetColumn() + { + int* array = stackalloc[] + { + 1, 2, 3, + 4, 5, 6 + }; + + RefEnumerable enumerable = new Span2D(array, 2, 3, 0).GetColumn(2); + + int[] expected = { 3, 6 }; + + CollectionAssert.AreEqual(enumerable.ToArray(), expected); + + Assert.ThrowsException(() => new Span2D(array, 2, 3, 0).GetColumn(-1)); + Assert.ThrowsException(() => new Span2D(array, 2, 3, 0).GetColumn(3)); + Assert.ThrowsException(() => new Span2D(array, 2, 3, 0).GetColumn(1000)); + } + [TestCategory("Span2DT")] [TestMethod] public void Test_Span2DT_GetEnumerator() @@ -818,6 +860,29 @@ public void Test_Span2DT_GetEnumerator() CollectionAssert.AreEqual(result, expected); } + [TestCategory("Span2DT")] + [TestMethod] + public unsafe void Test_Span2DT_Pointer_GetEnumerator() + { + int* array = stackalloc[] + { + 1, 2, 3, + 4, 5, 6 + }; + + int[] result = new int[4]; + int i = 0; + + foreach (var item in new Span2D(array + 1, 2, 2, 1)) + { + result[i++] = item; + } + + int[] expected = { 2, 3, 5, 6 }; + + CollectionAssert.AreEqual(result, expected); + } + [TestCategory("Span2DT")] [TestMethod] public void Test_Span2DT_GetEnumerator_Empty() From 52297bb358bd7019a1420efe6122ad88f4a69d4c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 24 Aug 2020 14:57:14 +0200 Subject: [PATCH 110/200] Added downlevel RuntimeHelpers support to HashCode --- .../Helpers/HashCode{T}.cs | 12 +++--------- .../Helpers/Test_HashCode{T}.cs | 2 -- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs b/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs index 3c2a43c32a4..6971b01fc98 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs @@ -9,6 +9,9 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; +#if !SPAN_RUNTIME_SUPPORT +using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; +#endif namespace Microsoft.Toolkit.HighPerformance.Helpers { @@ -25,14 +28,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers /// For more info, see . /// public struct HashCode -#if SPAN_RUNTIME_SUPPORT where T : notnull -#else - // If we lack the RuntimeHelpers.IsReferenceOrContainsReferences API, - // we need to constraint the generic type parameter to unmanaged, as we - // wouldn't otherwise be able to properly validate it at runtime. - where T : unmanaged -#endif { /// /// Gets a content hash from the input instance using the xxHash32 algorithm. @@ -61,7 +57,6 @@ internal static unsafe int CombineValues(ReadOnlySpan span) { ref T r0 = ref MemoryMarshal.GetReference(span); -#if SPAN_RUNTIME_SUPPORT // If typeof(T) is not unmanaged, iterate over all the items one by one. // This check is always known in advance either by the JITter or by the AOT // compiler, so this branch will never actually be executed by the code. @@ -69,7 +64,6 @@ internal static unsafe int CombineValues(ReadOnlySpan span) { return SpanHelper.GetDjb2HashCode(ref r0, (IntPtr)(void*)(uint)span.Length); } -#endif // Get the info for the target memory area to process. // The line below is computing the total byte size for the span, diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_HashCode{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_HashCode{T}.cs index 86828480387..26d148acd92 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_HashCode{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_HashCode{T}.cs @@ -63,7 +63,6 @@ public void Test_HashCodeOfT_VectorUnsupportedTypes_TestRepeat() TestForType(); } -#if NETCOREAPP3_1 [TestCategory("HashCodeOfT")] [TestMethod] public void Test_HashCodeOfT_ManagedType_TestRepeat() @@ -89,7 +88,6 @@ public void Test_HashCodeOfT_ManagedType_TestRepeat() Assert.AreEqual(hash1, hash2, $"Failed {typeof(string)} test with count {count}: got {hash1} and then {hash2}"); } } -#endif /// /// Performs a test for a specified type. From 7790b998c0fd14753dd5b044dddaf9d9ced15db8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 24 Aug 2020 16:08:25 +0200 Subject: [PATCH 111/200] Improved [ReadOnly]Memory2D.Equals(object) --- .../Memory/Memory2D{T}.cs | 12 +++++++++++- .../Memory/ReadOnlyMemory2D{T}.cs | 12 +++++++++++- .../Memory/Test_Memory2D{T}.cs | 5 +++++ .../Memory/Test_ReadOnlyMemory2D{T}.cs | 15 ++++++++++----- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index bab871d1eac..269fc27cd36 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -730,7 +730,17 @@ public bool TryGetMemory(out Memory memory) [EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object? obj) { - return obj is Memory2D memory && Equals(memory); + if (obj is Memory2D memory) + { + return Equals(memory); + } + + if (obj is ReadOnlyMemory2D readOnlyMemory) + { + return readOnlyMemory.Equals(this); + } + + return false; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 023436645c1..9b1219c43f1 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -789,7 +789,17 @@ public bool TryGetMemory(out ReadOnlyMemory memory) [EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object? obj) { - return obj is ReadOnlyMemory2D memory && Equals(memory); + if (obj is ReadOnlyMemory2D readOnlyMemory) + { + return Equals(readOnlyMemory); + } + + if (obj is Memory2D memory) + { + return Equals(memory); + } + + return false; } /// diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs index 6e1456f24cf..9907eef6632 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs @@ -404,6 +404,11 @@ public void Test_Memory2DT_Equals() Assert.IsFalse(memory2d.Equals(new Memory2D(array, 0, 1, 2, 2))); Assert.IsTrue(memory2d.Equals(new Memory2D(array))); Assert.IsTrue(memory2d.Equals(memory2d)); + + ReadOnlyMemory2D readOnlyMemory2d = memory2d; + + Assert.IsTrue(memory2d.Equals(readOnlyMemory2d)); + Assert.IsFalse(memory2d.Equals(readOnlyMemory2d.Slice(0, 1, 2, 2))); } [TestCategory("Memory2DT")] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs index 09dfd67f105..91a80a9d85a 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs @@ -398,12 +398,17 @@ public void Test_ReadOnlyMemory2DT_Equals() { 4, 5, 6 } }; - ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array); + ReadOnlyMemory2D readOnlyMemory2D = new ReadOnlyMemory2D(array); + + Assert.IsFalse(readOnlyMemory2D.Equals(null)); + Assert.IsFalse(readOnlyMemory2D.Equals(new ReadOnlyMemory2D(array, 0, 1, 2, 2))); + Assert.IsTrue(readOnlyMemory2D.Equals(new ReadOnlyMemory2D(array))); + Assert.IsTrue(readOnlyMemory2D.Equals(readOnlyMemory2D)); + + Memory2D memory2d = array; - Assert.IsFalse(memory2d.Equals(null)); - Assert.IsFalse(memory2d.Equals(new ReadOnlyMemory2D(array, 0, 1, 2, 2))); - Assert.IsTrue(memory2d.Equals(new ReadOnlyMemory2D(array))); - Assert.IsTrue(memory2d.Equals(memory2d)); + Assert.IsTrue(readOnlyMemory2D.Equals((object)memory2d)); + Assert.IsFalse(readOnlyMemory2D.Equals((object)memory2d.Slice(0, 1, 2, 2))); } [TestCategory("ReadOnlyMemory2DT")] From f4ae789c527987200ee94465a262b0bc3f9baf32 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 27 Aug 2020 11:48:24 +0200 Subject: [PATCH 112/200] Removed unnecessary generic type constraint --- .../Extensions/HashCodeExtensions.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/HashCodeExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/HashCodeExtensions.cs index c4c468162fb..2dfab655d8d 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/HashCodeExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/HashCodeExtensions.cs @@ -23,12 +23,7 @@ public static class HashCodeExtensions /// The input instance. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Add(ref this HashCode hashCode, ReadOnlySpan span) -#if SPAN_RUNTIME_SUPPORT where T : notnull -#else - // Same type constraints as HashCode, see comments there - where T : unmanaged -#endif { int hash = HashCode.CombineValues(span); From 70d5624ba2d58e7f26da5d2522422506e746be32 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 10 Sep 2020 18:32:02 +0200 Subject: [PATCH 113/200] Minor code refactoring --- .../Memory/ReadOnlySpan2D{T}.cs | 11 ++++------- Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs | 11 ++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index a88e0933c5b..2c738398f87 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -124,7 +124,7 @@ public unsafe ReadOnlySpan2D(void* pointer, int height, int width, int pitch) /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan2D(object instance, IntPtr offset, int height, int width, int pitch) + internal ReadOnlySpan2D(object? instance, IntPtr offset, int height, int width, int pitch) { #if SPAN_RUNTIME_SUPPORT this.span = MemoryMarshal.CreateReadOnlySpan(ref instance.DangerousGetObjectDataReferenceAt(offset), height); @@ -754,16 +754,13 @@ public ReadOnlySpan2D Slice(int row, int column, int width, int height) pitch = this.pitch + (this.width - width); #if SPAN_RUNTIME_SUPPORT - unsafe - { - ref T r0 = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.span), (IntPtr)(void*)(uint)shift); + ref T r0 = ref this.span.DangerousGetReferenceAt(shift); - return new ReadOnlySpan2D(r0, height, width, pitch); - } + return new ReadOnlySpan2D(r0, height, width, pitch); #else IntPtr offset = this.offset + (shift * Unsafe.SizeOf()); - return new ReadOnlySpan2D(this.instance!, offset, height, width, pitch); + return new ReadOnlySpan2D(this.instance, offset, height, width, pitch); #endif } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 187f473fec1..fd7252e08b9 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -153,7 +153,7 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Span2D(object instance, IntPtr offset, int height, int width, int pitch) + internal Span2D(object? instance, IntPtr offset, int height, int width, int pitch) { #if SPAN_RUNTIME_SUPPORT this.span = MemoryMarshal.CreateSpan(ref instance.DangerousGetObjectDataReferenceAt(offset), height); @@ -868,16 +868,13 @@ public Span2D Slice(int row, int column, int height, int width) pitch = this.Pitch + (this.width - width); #if SPAN_RUNTIME_SUPPORT - unsafe - { - ref T r0 = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.span), (IntPtr)(void*)(uint)shift); + ref T r0 = ref this.span.DangerousGetReferenceAt(shift); - return new Span2D(ref r0, height, width, pitch); - } + return new Span2D(ref r0, height, width, pitch); #else IntPtr offset = this.Offset + (shift * Unsafe.SizeOf()); - return new Span2D(this.Instance!, offset, height, width, pitch); + return new Span2D(this.Instance, offset, height, width, pitch); #endif } From 9a68e934abab35d092468583efe028d7668c426d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 10 Sep 2020 23:54:54 +0200 Subject: [PATCH 114/200] Minor code refactoring --- .../Memory/ReadOnlySpan2D{T}.cs | 6 ++---- Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 2c738398f87..9386d4f093e 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -115,6 +115,7 @@ public unsafe ReadOnlySpan2D(void* pointer, int height, int width, int pitch) this.pitch = pitch; } +#if !SPAN_RUNTIME_SUPPORT /// /// Initializes a new instance of the struct with the specified parameters. /// @@ -126,16 +127,13 @@ public unsafe ReadOnlySpan2D(void* pointer, int height, int width, int pitch) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ReadOnlySpan2D(object? instance, IntPtr offset, int height, int width, int pitch) { -#if SPAN_RUNTIME_SUPPORT - this.span = MemoryMarshal.CreateReadOnlySpan(ref instance.DangerousGetObjectDataReferenceAt(offset), height); -#else this.instance = instance; this.offset = offset; this.height = height; -#endif this.width = width; this.pitch = pitch; } +#endif /// /// Initializes a new instance of the struct. diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index fd7252e08b9..f2085ed250c 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -144,6 +144,7 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) this.Pitch = pitch; } +#if !SPAN_RUNTIME_SUPPORT /// /// Initializes a new instance of the struct with the specified parameters. /// @@ -155,16 +156,13 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Span2D(object? instance, IntPtr offset, int height, int width, int pitch) { -#if SPAN_RUNTIME_SUPPORT - this.span = MemoryMarshal.CreateSpan(ref instance.DangerousGetObjectDataReferenceAt(offset), height); -#else this.Instance = instance; this.Offset = offset; this.height = height; -#endif this.width = width; this.Pitch = pitch; } +#endif /// /// Initializes a new instance of the struct. From ef942b861247853ab7725c0b4468cff877f1b8db Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 11 Sep 2020 00:11:29 +0200 Subject: [PATCH 115/200] Switched Memory2D constructor to MemoryManager --- .../Memory/Memory2D{T}.cs | 94 ++++++------------- 1 file changed, 27 insertions(+), 67 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 269fc27cd36..7268d091c12 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -305,23 +305,23 @@ public Memory2D(T[,,] array, int depth, int row, int column, int height, int wid /// /// Initializes a new instance of the struct. /// - /// The target to wrap. + /// The target to wrap. /// The height of the resulting 2D area. /// The width of each row in the resulting 2D area. /// /// Thrown when either or are invalid. /// - /// The total area must match the lenght of . - public Memory2D(Memory memory, int height, int width) - : this(memory, 0, height, width, 0) + /// The total area must match the lenght of . + public Memory2D(MemoryManager memoryManager, int height, int width) + : this(memoryManager, 0, height, width, 0) { } /// /// Initializes a new instance of the struct. /// - /// The target to wrap. - /// The initial offset within . + /// The target to wrap. + /// 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. @@ -329,11 +329,13 @@ public Memory2D(Memory memory, int height, int width) /// Thrown when one of the input parameters is out of range. /// /// - /// Thrown when the requested area is outside of bounds for . + /// Thrown when the requested area is outside of bounds for . /// - public Memory2D(Memory memory, int offset, int height, int width, int pitch) + public Memory2D(MemoryManager memoryManager, int offset, int height, int width, int pitch) { - if ((uint)offset > (uint)memory.Length) + int length = memoryManager.GetSpan().Length; + + if ((uint)offset > (uint)length) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); } @@ -361,7 +363,7 @@ public Memory2D(Memory memory, int offset, int height, int width, int pitch) } int - remaining = memory.Length - offset, + remaining = length - offset, area = ((width + pitch) * (height - 1)) + width; if (area > remaining) @@ -369,33 +371,11 @@ public Memory2D(Memory memory, int offset, int height, int width, int pitch) ThrowHelper.ThrowArgumentException(); } - // Check if the Memory instance wraps a string. This is possible in case - // consumers do an unsafe cast for the entire Memory object, and while not - // really safe it is still supported in CoreCLR too, so we're following suit here. - if (typeof(T) == typeof(char) && - MemoryMarshal.TryGetString(Unsafe.As, Memory>(ref memory), out string? text, out int start, out _)) - { - ref char r0 = ref text.DangerousGetReferenceAt(start + offset); - - this.instance = text; - this.offset = text.DangerousGetObjectDataByteOffset(ref r0); - } - else if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) - { - // Check if the input Memory instance wraps an array we can access. - // This is fine, since Memory on its own doesn't control the lifetime - // of the underlying array anyway, and this Memory2D type would do the same. - // Using the array directly makes retrieving a Span2D faster down the line, - // as we no longer have to jump through the boxed Memory first anymore. - T[] array = segment.Array!; + this.instance = memoryManager; - this.instance = array; - this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)) + segment.Offset; - } - else + unsafe { - this.instance = memory.Slice(offset); - this.offset = default; + this.offset = (IntPtr)(void*)(uint)offset; } this.height = height; @@ -432,25 +412,12 @@ private Memory2D(object instance, IntPtr offset, int height, int width, int pitc /// The pitch of the 2D memory area to map. /// A instance with the specified parameters. /// The parameter is not validated, and it's responsability of the caller to ensure it's valid. - /// - /// Thrown when is of an unsupported type. - /// /// /// Thrown when one of the input parameters is out of range. /// [Pure] public static Memory2D DangerousCreate(object instance, ref T value, int height, int width, int pitch) { - if (instance.GetType() == typeof(Memory)) - { - ThrowHelper.ThrowArgumentExceptionForUnsupportedType(); - } - - if (instance.GetType() == typeof(ReadOnlyMemory)) - { - ThrowHelper.ThrowArgumentExceptionForUnsupportedType(); - } - if (height < 0) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); @@ -523,14 +490,12 @@ public Span2D Span if (!(this.instance is null)) { #if SPAN_RUNTIME_SUPPORT - if (this.instance.GetType() == typeof(Memory)) + if (this.instance is MemoryManager memoryManager) { - Memory memory = (Memory)this.instance; + ref T r0 = ref memoryManager.GetSpan().DangerousGetReference(); + ref T r1 = ref Unsafe.Add(ref r0, this.offset); - // If the wrapped object is a Memory, it is always pre-offset - ref T r0 = ref memory.Span.DangerousGetReference(); - - return new Span2D(ref r0, this.height, this.width, this.pitch); + return new Span2D(ref r1, this.height, this.width, this.pitch); } else { @@ -588,14 +553,6 @@ public Memory2D Slice(int row, int column, int height, int width) IntPtr offset = this.offset + (shift * Unsafe.SizeOf()); - if (this.instance is Memory memory) - { - // Memory instance are always already sliced - object instance = memory.Slice((int)offset); - - return new Memory2D(instance, default, height, width, pitch); - } - return new Memory2D(this.instance!, offset, height, width, pitch); } @@ -646,9 +603,9 @@ public unsafe MemoryHandle Pin() { if (!(this.instance is null)) { - if (this.instance is Memory memory) + if (this.instance is MemoryManager memoryManager) { - return memory.Pin(); + return memoryManager.Pin(); } GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned); @@ -689,10 +646,13 @@ public bool TryGetMemory(out Memory memory) // to make sure not to ever actually write to the resulting Memory. memory = MemoryMarshal.AsMemory(Unsafe.As, Memory>(ref temp)); } - else if (this.instance.GetType() == typeof(Memory)) + else if (this.instance is MemoryManager memoryManager) { - // If the object is a Memory, just slice it as needed - memory = ((Memory)this.instance).Slice(0, this.height * this.width); + unsafe + { + // If the object is a MemoryManager, just slice it as needed + memory = memoryManager.Memory.Slice((int)(void*)this.offset, this.height * this.width); + } } else if (this.instance.GetType() == typeof(T[])) { From ec3c0ae2b96adab493a98f523da1f83aeb18b53d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 11 Sep 2020 00:12:22 +0200 Subject: [PATCH 116/200] Fixed a bug in [ReadOnly]Memory2D.TryGetMemory Need the explicit "in" to pick the right overload --- Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs | 2 +- Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 7268d091c12..3b77481e378 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -635,7 +635,7 @@ public bool TryGetMemory(out Memory memory) else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string)) { string text = Unsafe.As(this.instance); - int index = text.AsSpan().IndexOf(text.DangerousGetObjectDataReferenceAt(this.offset)); + int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt(this.offset)); ReadOnlyMemory temp = text.AsMemory(index, Size); // The string type could still be present if a user ends up creating a diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 9b1219c43f1..4f48e2f7ee9 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -743,7 +743,7 @@ public bool TryGetMemory(out ReadOnlyMemory memory) else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string)) { string text = Unsafe.As(this.instance); - int index = text.AsSpan().IndexOf(text.DangerousGetObjectDataReferenceAt(this.offset)); + int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt(this.offset)); ReadOnlyMemory temp = text.AsMemory(index, Size); memory = Unsafe.As, ReadOnlyMemory>(ref temp); From 5d2c5aa52914f498b9265956297b3e4cbde6e4fc Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 11 Sep 2020 00:52:44 +0200 Subject: [PATCH 117/200] Added Memory.AsMemory2D extensions --- .../Extensions/MemoryExtensions.cs | 45 +++++++ .../Memory/Memory2D{T}.cs | 112 ++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs index 794339fbbed..448d0368618 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs @@ -6,6 +6,7 @@ using System.Diagnostics.Contracts; using System.IO; using System.Runtime.CompilerServices; +using Microsoft.Toolkit.HighPerformance.Memory; using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream; namespace Microsoft.Toolkit.HighPerformance.Extensions @@ -15,6 +16,50 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions /// public static class MemoryExtensions { + /// + /// 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 Memory2D AsMemory2D(this Memory memory, int height, int width) + { + return new Memory2D(memory, 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 Memory2D AsMemory2D(this Memory memory, int offset, int height, int width, int pitch) + { + return new Memory2D(memory, offset, height, width, pitch); + } + /// /// Returns a wrapping the contents of the given of instance. /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 3b77481e378..1b6f8bff392 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -382,6 +382,118 @@ public Memory2D(MemoryManager memoryManager, int offset, int height, int widt this.width = width; this.pitch = pitch; } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. + /// + /// Thrown when either or are invalid. + /// + /// The total area must match the lenght of . + internal Memory2D(Memory memory, int height, int width) + : this(memory, 0, height, width, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// 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. + /// + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . + /// + internal Memory2D(Memory memory, int offset, int height, int width, int pitch) + { + if ((uint)offset > (uint)memory.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + if (width == 0 || height == 0) + { + this = default; + + return; + } + + int + remaining = memory.Length - offset, + area = ((width + pitch) * (height - 1)) + width; + + if (area > remaining) + { + ThrowHelper.ThrowArgumentException(); + } + + // Check if the Memory instance wraps a string. This is possible in case + // consumers do an unsafe cast for the entire Memory object, and while not + // really safe it is still supported in CoreCLR too, so we're following suit here. + if (typeof(T) == typeof(char) && + MemoryMarshal.TryGetString(Unsafe.As, Memory>(ref memory), out string? text, out int textStart, out _)) + { + ref char r0 = ref text.DangerousGetReferenceAt(textStart + offset); + + this.instance = text; + this.offset = text.DangerousGetObjectDataByteOffset(ref r0); + } + else if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) + { + // Check if the input Memory instance wraps an array we can access. + // This is fine, since Memory on its own doesn't control the lifetime + // of the underlying array anyway, and this Memory2D type would do the same. + // Using the array directly makes retrieving a Span2D faster down the line, + // as we no longer have to jump through the boxed Memory first anymore. + T[] array = segment.Array!; + + this.instance = array; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(segment.Offset + offset)); + } + else if (MemoryMarshal.TryGetMemoryManager>(memory, out var memoryManager, out int memoryManagerStart, out _)) + { + this.instance = memoryManager; + + unsafe + { + this.offset = (IntPtr)(void*)(uint)(memoryManagerStart + offset); + } + } + else + { + ThrowHelper.ThrowArgumentExceptionForUnsupportedType(); + + this.instance = null; + this.offset = default; + } + + this.height = height; + this.width = width; + this.pitch = pitch; + } #endif /// From a39669ed258d9a4a1343c46f1450be8092e4b11b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 11 Sep 2020 00:55:06 +0200 Subject: [PATCH 118/200] Added missing compiler directive --- .../Extensions/MemoryExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs index 448d0368618..cb246453660 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs @@ -16,6 +16,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions /// public static class MemoryExtensions { +#if SPAN_RUNTIME_SUPPORT /// /// Returns a instance wrapping the underlying data for the given instance. /// @@ -59,6 +60,7 @@ public static Memory2D AsMemory2D(this Memory memory, int offset, int h { return new Memory2D(memory, offset, height, width, pitch); } +#endif /// /// Returns a wrapping the contents of the given of instance. From 6d25a63311c9362302135674c803285ed384da22 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 11 Sep 2020 01:03:16 +0200 Subject: [PATCH 119/200] Added MemoryManager support to ReadOnlyMemory2D --- .../Extensions/ReadOnlyMemoryExtensions.cs | 48 ++++++ .../Memory/ReadOnlyMemory2D{T}.cs | 152 +++++++++++++----- 2 files changed, 160 insertions(+), 40 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs index eab50592776..ab6214e75c1 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs @@ -5,6 +5,8 @@ using System; using System.Diagnostics.Contracts; using System.IO; +using System.Runtime.CompilerServices; +using Microsoft.Toolkit.HighPerformance.Memory; using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream; namespace Microsoft.Toolkit.HighPerformance.Extensions @@ -14,6 +16,52 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions /// public static class ReadOnlyMemoryExtensions { +#if SPAN_RUNTIME_SUPPORT + /// + /// 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 ReadOnlyMemory2D AsMemory2D(this ReadOnlyMemory memory, int height, int width) + { + return new ReadOnlyMemory2D(memory, 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 ReadOnlyMemory2D AsMemory2D(this ReadOnlyMemory memory, int offset, int height, int width, int pitch) + { + return new ReadOnlyMemory2D(memory, offset, height, width, pitch); + } +#endif + /// /// Returns a wrapping the contents of the given of instance. /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 4f48e2f7ee9..a05a4d3aeca 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -376,14 +376,95 @@ public ReadOnlyMemory2D(T[,,] array, int depth, int row, int column, int height, /// /// Initializes a new instance of the struct. /// - /// The target to wrap. + /// The target to wrap. + /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. + /// + /// Thrown when either or are invalid. + /// + /// The total area must match the lenght of . + public ReadOnlyMemory2D(MemoryManager memoryManager, int height, int width) + : this(memoryManager, 0, height, width, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// 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. + /// + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . + /// + public ReadOnlyMemory2D(MemoryManager memoryManager, int offset, int height, int width, int pitch) + { + int length = memoryManager.GetSpan().Length; + + if ((uint)offset > (uint)length) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + if (width == 0 || height == 0) + { + this = default; + + return; + } + + int + remaining = length - offset, + area = ((width + pitch) * (height - 1)) + width; + + if (area > remaining) + { + ThrowHelper.ThrowArgumentException(); + } + + this.instance = memoryManager; + + unsafe + { + this.offset = (IntPtr)(void*)(uint)offset; + } + + this.height = height; + this.width = width; + this.pitch = pitch; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. /// The height of the resulting 2D area. /// The width of each row in the resulting 2D area. /// /// Thrown when either or are invalid. /// /// The total area must match the lenght of . - public ReadOnlyMemory2D(ReadOnlyMemory memory, int height, int width) + internal ReadOnlyMemory2D(ReadOnlyMemory memory, int height, int width) : this(memory, 0, height, width, 0) { } @@ -402,7 +483,7 @@ public ReadOnlyMemory2D(ReadOnlyMemory memory, int height, int width) /// /// Thrown when the requested area is outside of bounds for . /// - public ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int height, int width, int pitch) + internal ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int height, int width, int pitch) { if ((uint)offset > (uint)memory.Length) { @@ -440,26 +521,36 @@ public ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int height, int wi ThrowHelper.ThrowArgumentException(); } - // Check whether the memory wraps a string we can directly access + // Check the supported underlying objects, like in Memory2D if (typeof(T) == typeof(char) && - MemoryMarshal.TryGetString(Unsafe.As, ReadOnlyMemory>(ref memory), out string? text, out int start, out _)) + MemoryMarshal.TryGetString(Unsafe.As, ReadOnlyMemory>(ref memory), out string? text, out int textStart, out _)) { - ref char r0 = ref text.DangerousGetReferenceAt(start + offset); + ref char r0 = ref text.DangerousGetReferenceAt(textStart + offset); this.instance = text; this.offset = text.DangerousGetObjectDataByteOffset(ref r0); } else if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) { - // Access the array directly, if possible, just like in Memory2D T[] array = segment.Array!; this.instance = array; - this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset)) + segment.Offset; + this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(segment.Offset + offset)); + } + else if (MemoryMarshal.TryGetMemoryManager>(memory, out var memoryManager, out int memoryManagerStart, out _)) + { + this.instance = memoryManager; + + unsafe + { + this.offset = (IntPtr)(void*)(uint)(memoryManagerStart + offset); + } } else { - this.instance = memory.Slice(offset); + ThrowHelper.ThrowArgumentExceptionForUnsupportedType(); + + this.instance = null; this.offset = default; } @@ -497,25 +588,12 @@ private ReadOnlyMemory2D(object instance, IntPtr offset, int height, int width, /// The pitch of the 2D memory area to map. /// A instance with the specified parameters. /// The parameter is not validated, and it's responsability of the caller to ensure it's valid. - /// - /// Thrown when is of an unsupported type. - /// /// /// Thrown when one of the input parameters is out of range. /// [Pure] public static ReadOnlyMemory2D DangerousCreate(object instance, ref T value, int height, int width, int pitch) { - if (instance.GetType() == typeof(Memory)) - { - ThrowHelper.ThrowArgumentExceptionForUnsupportedType(); - } - - if (instance.GetType() == typeof(ReadOnlyMemory)) - { - ThrowHelper.ThrowArgumentExceptionForUnsupportedType(); - } - if (height < 0) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); @@ -588,14 +666,12 @@ public ReadOnlySpan2D Span if (!(this.instance is null)) { #if SPAN_RUNTIME_SUPPORT - if (this.instance.GetType() == typeof(ReadOnlyMemory)) + if (this.instance is MemoryManager memoryManager) { - ReadOnlyMemory memory = (ReadOnlyMemory)this.instance; + ref T r0 = ref memoryManager.GetSpan().DangerousGetReference(); + ref T r1 = ref Unsafe.Add(ref r0, this.offset); - // If the wrapped object is a ReadOnlyMemory, it is always pre-offset - ref T r0 = ref memory.Span.DangerousGetReference(); - - return new ReadOnlySpan2D(r0, this.height, this.width, this.pitch); + return new ReadOnlySpan2D(r1, this.height, this.width, this.pitch); } else { @@ -654,13 +730,6 @@ public ReadOnlyMemory2D Slice(int row, int column, int height, int width) IntPtr offset = this.offset + (shift * Unsafe.SizeOf()); - if (this.instance!.GetType() == typeof(ReadOnlyMemory)) - { - object instance = ((ReadOnlyMemory)this.instance).Slice((int)offset); - - return new ReadOnlyMemory2D(instance, default, height, width, pitch); - } - return new ReadOnlyMemory2D(this.instance!, offset, height, width, pitch); } @@ -711,9 +780,9 @@ public unsafe MemoryHandle Pin() { if (!(this.instance is null)) { - if (this.instance.GetType() == typeof(ReadOnlyMemory)) + if (this.instance is MemoryManager memoryManager) { - return ((ReadOnlyMemory)this.instance).Pin(); + return memoryManager.Pin(); } GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned); @@ -748,10 +817,13 @@ public bool TryGetMemory(out ReadOnlyMemory memory) memory = Unsafe.As, ReadOnlyMemory>(ref temp); } - else if (this.instance.GetType() == typeof(ReadOnlyMemory)) + else if (this.instance is MemoryManager memoryManager) { - // If the object is a ReadOnlyMemory, just slice it as needed - memory = ((ReadOnlyMemory)this.instance).Slice(0, this.height * this.width); + unsafe + { + // If the object is a MemoryManager, just slice it as needed + memory = memoryManager.Memory.Slice((int)(void*)this.offset, this.height * this.width); + } } else if (this.instance.GetType() == typeof(T[])) { From fb1f189a04dfb0f0007dbad1f20ae461235a4cce Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 11 Sep 2020 01:08:59 +0200 Subject: [PATCH 120/200] Fixed build errors in unit tests --- .../Memory/Test_Memory2D{T}.cs | 15 ++++++++------- .../Memory/Test_ReadOnlyMemory2D{T}.cs | 15 ++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs index 9907eef6632..3fae3732bb4 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -173,7 +174,7 @@ public void Test_Memory2DT_MemoryConstructor() 1, 2, 3, 4, 5, 6 }; - Memory2D memory2d = new Memory2D(memory, 1, 2, 2, 1); + Memory2D memory2d = memory.AsMemory2D(1, 2, 2, 1); Assert.IsFalse(memory2d.IsEmpty); Assert.AreEqual(memory2d.Size, 4); @@ -182,11 +183,11 @@ public void Test_Memory2DT_MemoryConstructor() Assert.AreEqual(memory2d.Span[0, 0], 2); Assert.AreEqual(memory2d.Span[1, 1], 6); - Assert.ThrowsException(() => new Memory2D(memory, -99, 1, 1, 1)); - Assert.ThrowsException(() => new Memory2D(memory, 0, -10, 1, 1)); - Assert.ThrowsException(() => new Memory2D(memory, 0, 1, 1, -1)); - Assert.ThrowsException(() => new Memory2D(memory, 0, 1, -100, 1)); - Assert.ThrowsException(() => new Memory2D(memory, 0, 10, 1, 120)); + Assert.ThrowsException(() => memory.AsMemory2D(-99, 1, 1, 1)); + Assert.ThrowsException(() => memory.AsMemory2D(0, -10, 1, 1)); + Assert.ThrowsException(() => memory.AsMemory2D(0, 1, 1, -1)); + Assert.ThrowsException(() => memory.AsMemory2D(0, 1, -100, 1)); + Assert.ThrowsException(() => memory.AsMemory2D(0, 10, 1, 120)); } #endif @@ -304,7 +305,7 @@ public void Test_Memory2DT_TryGetMemory_3() { Memory data = new[] { 1, 2, 3, 4 }; - Memory2D memory2d = new Memory2D(data, 2, 2); + Memory2D memory2d = data.AsMemory2D(2, 2); bool success = memory2d.TryGetMemory(out Memory memory); diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs index 91a80a9d85a..0e360a00419 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -173,7 +174,7 @@ public void Test_ReadOnlyMemory2DT_ReadOnlyMemoryConstructor() 1, 2, 3, 4, 5, 6 }; - ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(memory, 1, 2, 2, 1); + ReadOnlyMemory2D memory2d = memory.AsMemory2D(1, 2, 2, 1); Assert.IsFalse(memory2d.IsEmpty); Assert.AreEqual(memory2d.Size, 4); @@ -182,11 +183,11 @@ public void Test_ReadOnlyMemory2DT_ReadOnlyMemoryConstructor() Assert.AreEqual(memory2d.Span[0, 0], 2); Assert.AreEqual(memory2d.Span[1, 1], 6); - Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, -99, 1, 1, 1)); - Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, 0, -10, 1, 1)); - Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, 0, 1, 1, -1)); - Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, 0, 1, -100, 1)); - Assert.ThrowsException(() => new ReadOnlyMemory2D(memory, 0, 10, 1, 120)); + Assert.ThrowsException(() => memory.AsMemory2D(-99, 1, 1, 1)); + Assert.ThrowsException(() => memory.AsMemory2D(0, -10, 1, 1)); + Assert.ThrowsException(() => memory.AsMemory2D(0, 1, 1, -1)); + Assert.ThrowsException(() => memory.AsMemory2D(0, 1, -100, 1)); + Assert.ThrowsException(() => memory.AsMemory2D(0, 10, 1, 120)); } #endif @@ -304,7 +305,7 @@ public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_3() { ReadOnlyMemory data = new[] { 1, 2, 3, 4 }; - ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(data, 2, 2); + ReadOnlyMemory2D memory2d = data.AsMemory2D(2, 2); bool success = memory2d.TryGetMemory(out ReadOnlyMemory memory); From 401043db247a7c44770a42ee6fa9645c6b74c4f0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 9 Oct 2020 16:13:25 +0200 Subject: [PATCH 121/200] Fixed ND array offsetting in portable version --- .../Extensions/ArrayExtensions.2D.cs | 29 ++++---- .../Extensions/ArrayExtensions.3D.cs | 27 +++---- .../Helpers/Internals/RuntimeHelpers.cs | 70 +++++++++++++++++-- .../Memory/ReadOnlyMemory2D{T}.cs | 3 +- 4 files changed, 97 insertions(+), 32 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 4e6c2ba8e9d..9213a30ae79 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -12,6 +12,9 @@ using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; using Microsoft.Toolkit.HighPerformance.Memory; +#if !NETCORE_RUNTIME +using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; +#endif namespace Microsoft.Toolkit.HighPerformance.Extensions { @@ -37,17 +40,9 @@ public static ref T DangerousGetReference(this T[,] array) 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]; - } + IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset(); - unsafe - { - return ref Unsafe.AsRef(null); - } -#pragma warning restore SA1131 + return ref array.DangerousGetObjectDataReferenceAt(offset); #endif } @@ -67,15 +62,19 @@ public static ref T DangerousGetReference(this T[,] array) /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe ref T DangerousGetReferenceAt(this T[,] array, int i, int j) + 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, (IntPtr)(void*)(uint)offset); - return ref ri; + unsafe + { + ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); + + return ref ri; + } #else if ((uint)i < (uint)array.GetLength(0) && (uint)j < (uint)array.GetLength(1)) @@ -83,7 +82,9 @@ public static unsafe ref T DangerousGetReferenceAt(this T[,] array, int i, in return ref array[i, j]; } - return ref Unsafe.AsRef(null); + IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset(); + + return ref array.DangerousGetObjectDataReferenceAt(offset); #endif } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index e77df98b950..ecce7c2a9b9 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -11,6 +11,9 @@ #endif using Microsoft.Toolkit.HighPerformance.Helpers.Internals; using Microsoft.Toolkit.HighPerformance.Memory; +#if !NETCORE_RUNTIME +using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; +#endif namespace Microsoft.Toolkit.HighPerformance.Extensions { @@ -36,15 +39,9 @@ public static ref T DangerousGetReference(this T[,,] array) return ref r0; #else - if (array.Length > 0) - { - return ref array[0, 0, 0]; - } + IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset(); - unsafe - { - return ref Unsafe.AsRef(null); - } + return ref array.DangerousGetObjectDataReferenceAt(offset); #endif } @@ -65,15 +62,19 @@ public static ref T DangerousGetReference(this T[,,] array) /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe ref T DangerousGetReferenceAt(this T[,,] array, int i, int j, int k) + public static ref T DangerousGetReferenceAt(this T[,,] array, int i, int j, int k) { #if NETCORE_RUNTIME var arrayData = Unsafe.As(array); int offset = (i * arrayData.Height * arrayData.Width) + (j * arrayData.Width) + k; ref T r0 = ref Unsafe.As(ref arrayData.Data); - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); - return ref ri; + unsafe + { + ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); + + return ref ri; + } #else if ((uint)i < (uint)array.GetLength(0) && (uint)j < (uint)array.GetLength(1) && @@ -82,7 +83,9 @@ public static unsafe ref T DangerousGetReferenceAt(this T[,,] array, int i, i return ref array[i, j, k]; } - return ref Unsafe.AsRef(null); + IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset(); + + return ref array.DangerousGetObjectDataReferenceAt(offset); #endif } diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs index 1b0bda97b30..31b8f755bfd 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs @@ -7,11 +7,11 @@ // The portable implementation in this type is originally from CoreFX. // See https://github.com/dotnet/corefx/blob/release/2.1/src/System.Memory/src/System/SpanHelpers.cs. -#if !SPAN_RUNTIME_SUPPORT - using System; using System.Diagnostics.Contracts; +#if !SPAN_RUNTIME_SUPPORT using System.Reflection; +#endif using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Extensions; @@ -22,6 +22,31 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals /// internal static class RuntimeHelpers { + /// + /// Gets the byte offset to the first element in a 2D array. + /// + /// The type of values in the array. + /// The byte offset to the first element in a 2D array. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IntPtr GetArray2DDataByteOffset() + { + return TypeInfo.Array2DDataByteOffset; + } + + /// + /// Gets the byte offset to the first element in a 3D array. + /// + /// The type of values in the array. + /// The byte offset to the first element in a 3D array. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IntPtr GetArray3DDataByteOffset() + { + return TypeInfo.Array3DDataByteOffset; + } + +#if !SPAN_RUNTIME_SUPPORT /// /// Gets a byte offset describing a portable pinnable reference. This can either be an /// interior pointer into some object data (described with a valid reference @@ -127,6 +152,7 @@ private static bool IsReferenceOrContainsReferences(Type type) return false; } +#endif /// /// A private generic class to preload type info for arbitrary runtime types. @@ -134,12 +160,46 @@ private static bool IsReferenceOrContainsReferences(Type type) /// The type to load info for. private static class TypeInfo { + /// + /// The byte offset to the first element in a 2D array. + /// + public static readonly IntPtr Array2DDataByteOffset = MeasureArray2DDataByteOffset(); + + /// + /// The byte offset to the first element in a 3D array. + /// + public static readonly IntPtr Array3DDataByteOffset = MeasureArray3DDataByteOffset(); + +#if !SPAN_RUNTIME_SUPPORT /// /// Indicates whether does not respect the constraint. /// public static readonly bool IsReferenceOrContainsReferences = IsReferenceOrContainsReferences(typeof(T)); +#endif + + /// + /// Computes the value for . + /// + /// The value of for the current runtime. + [Pure] + private static IntPtr MeasureArray2DDataByteOffset() + { + var array = new T[1, 1]; + + return array.DangerousGetObjectDataByteOffset(ref array[0, 0]); + } + + /// + /// Computes the value for . + /// + /// The value of for the current runtime. + [Pure] + private static IntPtr MeasureArray3DDataByteOffset() + { + var array = new T[1, 1, 1]; + + return array.DangerousGetObjectDataByteOffset(ref array[0, 0, 0]); + } } } -} - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index a05a4d3aeca..7700e6a78d1 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -12,6 +12,7 @@ using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory.Internals; using Microsoft.Toolkit.HighPerformance.Memory.Views; +using static Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; namespace Microsoft.Toolkit.HighPerformance.Memory { @@ -229,7 +230,7 @@ public ReadOnlyMemory2D(T[,]? array) } this.instance = array; - this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(0, 0)); + this.offset = GetArray2DDataByteOffset(); this.height = array.GetLength(0); this.width = array.GetLength(1); this.pitch = 0; From 241a923e84fa044cf63630872d24170562b782ff Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 9 Oct 2020 16:27:00 +0200 Subject: [PATCH 122/200] Added T[,]/T[,,].AsMemory() extensions --- .../Internals/RawObjectMemoryManager.cs | 93 +++++++++++++++++++ .../Extensions/ArrayExtensions.2D.cs | 16 +++- .../Extensions/ArrayExtensions.3D.cs | 18 +++- .../Helpers/HashCode{T}.cs | 4 +- 4 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager.cs diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager.cs new file mode 100644 index 00000000000..795ea6da4a5 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager.cs @@ -0,0 +1,93 @@ +// 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. + +#if SPAN_RUNTIME_SUPPORT + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.Toolkit.HighPerformance.Extensions; + +namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals +{ + /// + /// A custom that can wrap arbitrary instances. + /// + /// The type of elements in the target memory area. + internal sealed class RawObjectMemoryManager : MemoryManager + { + /// + /// The target instance. + /// + private readonly object instance; + + /// + /// The initial offset within . + /// + private readonly IntPtr offset; + + /// + /// The length of the target memory area. + /// + private readonly int length; + + /// + /// Initializes a new instance of the class. + /// + /// The target instance. + /// The starting offset within . + /// The usable length within . + public RawObjectMemoryManager(object instance, IntPtr offset, int length) + { + this.instance = instance; + this.offset = offset; + this.length = length; + } + + /// + public override Span GetSpan() + { + ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset); + + return MemoryMarshal.CreateSpan(ref r0, this.length); + } + + /// + public override unsafe MemoryHandle Pin(int elementIndex = 0) + { + if ((uint)elementIndex >= (uint)this.length) + { + ThrowArgumentOutOfRangeExceptionForInvalidElementIndex(); + } + + ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset); + ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)elementIndex); + void* p = Unsafe.AsPointer(ref r1); + GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned); + + return new MemoryHandle(p, handle); + } + + /// + public override void Unpin() + { + } + + /// + protected override void Dispose(bool disposing) + { + } + + /// + /// Throws an when the input index for is not valid. + /// + private static void ThrowArgumentOutOfRangeExceptionForInvalidElementIndex() + { + throw new ArgumentOutOfRangeException("elementIndex", "The input element index was not in the valid range"); + } + } +} + +#endif diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 9213a30ae79..a19348981b8 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -8,11 +8,12 @@ using System.Runtime.CompilerServices; #if SPAN_RUNTIME_SUPPORT using System.Runtime.InteropServices; +using Microsoft.Toolkit.HighPerformance.Buffers.Internals; #endif using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; using Microsoft.Toolkit.HighPerformance.Memory; -#if !NETCORE_RUNTIME +#if !NETCORE_RUNTIME || SPAN_RUNTIME_SUPPORT using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; #endif @@ -345,6 +346,19 @@ public static Span GetRowSpan(this T[,] array, int row) return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1)); } + /// + /// Creates 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 Memory AsMemory(this T[,] array) + { + return new RawObjectMemoryManager(array, RuntimeHelpers.GetArray2DDataByteOffset(), array.Length).Memory; + } + /// /// Creates a new over an input 2D array. /// diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index ecce7c2a9b9..d5938348287 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -8,10 +8,11 @@ using Microsoft.Toolkit.HighPerformance.Enumerables; #if SPAN_RUNTIME_SUPPORT using System.Runtime.InteropServices; +using Microsoft.Toolkit.HighPerformance.Buffers.Internals; #endif using Microsoft.Toolkit.HighPerformance.Helpers.Internals; using Microsoft.Toolkit.HighPerformance.Memory; -#if !NETCORE_RUNTIME +#if !NETCORE_RUNTIME || SPAN_RUNTIME_SUPPORT using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; #endif @@ -111,6 +112,19 @@ private sealed class RawArray3DData #endif #if SPAN_RUNTIME_SUPPORT + /// + /// Creates a new over an input 3D array. + /// + /// The type of elements in the input 3D array instance. + /// The input 3D array instance. + /// A instance with the values of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory AsMemory(this T[,,] array) + { + return new RawObjectMemoryManager(array, RuntimeHelpers.GetArray3DDataByteOffset(), array.Length).Memory; + } + /// /// Creates a new over an input 3D array. /// @@ -139,9 +153,7 @@ public static Span AsSpan(this T[,,] array) #endif return MemoryMarshal.CreateSpan(ref r0, length); } -#endif -#if SPAN_RUNTIME_SUPPORT /// /// Creates a new instance of the struct wrapping a layer in a 3D array. /// diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs b/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs index 6971b01fc98..1f26854fbb3 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs @@ -9,7 +9,9 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; -#if !SPAN_RUNTIME_SUPPORT +#if SPAN_RUNTIME_SUPPORT +using RuntimeHelpers = System.Runtime.CompilerServices.RuntimeHelpers; +#else using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; #endif From aaf5fb3a385e6a862986477c802002fc75825a9c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 9 Oct 2020 16:34:48 +0200 Subject: [PATCH 123/200] Improved Memory2D.TryGetMemory for ND arrays --- .../Memory/Memory2D{T}.cs | 13 +++++++++++++ .../Memory/ReadOnlyMemory2D{T}.cs | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 1b6f8bff392..9d67feee0bc 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -9,6 +9,9 @@ using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#if SPAN_RUNTIME_SUPPORT +using Microsoft.Toolkit.HighPerformance.Buffers.Internals; +#endif using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory.Internals; using Microsoft.Toolkit.HighPerformance.Memory.Views; @@ -774,6 +777,16 @@ public bool TryGetMemory(out Memory memory) memory = array.AsMemory(index, this.height * this.width); } +#if SPAN_RUNTIME_SUPPORT + else if (this.instance.GetType() == typeof(T[,]) || + this.instance.GetType() == typeof(T[,,])) + { + // If the object is a 2D or 3D array, we can create a Memory from the RawObjectMemoryManager type. + // We just need to use the precomputed offset pointing to the first item in the current instance, + // and the current usable length. We don't need to retrieve the current index, as the manager just offsets. + memory = new RawObjectMemoryManager(this.instance, this.offset, this.height * this.width).Memory; + } +#endif else { // Reuse a single failure path to reduce diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 7700e6a78d1..e640fa7f275 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -9,6 +9,9 @@ using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#if SPAN_RUNTIME_SUPPORT +using Microsoft.Toolkit.HighPerformance.Buffers.Internals; +#endif using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory.Internals; using Microsoft.Toolkit.HighPerformance.Memory.Views; @@ -834,6 +837,13 @@ public bool TryGetMemory(out ReadOnlyMemory memory) memory = array.AsMemory(index, this.height * this.width); } +#if SPAN_RUNTIME_SUPPORT + else if (this.instance.GetType() == typeof(T[,]) || + this.instance.GetType() == typeof(T[,,])) + { + memory = new RawObjectMemoryManager(this.instance, this.offset, this.height * this.width).Memory; + } +#endif else { // Reuse a single failure path to reduce From 3562f7361d1de0510475023342b9d2bd65a626eb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 9 Oct 2020 19:15:06 +0200 Subject: [PATCH 124/200] Fixed unit tests --- .../Memory/Test_Memory2D{T}.cs | 6 ++++-- .../Memory/Test_ReadOnlyMemory2D{T}.cs | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs index 3fae3732bb4..2d566d610cf 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -279,8 +280,9 @@ public void Test_Memory2DT_TryGetMemory_1() bool success = memory2d.TryGetMemory(out Memory memory); - Assert.IsFalse(success); - Assert.IsTrue(memory.IsEmpty); + Assert.IsTrue(success); + Assert.AreEqual(memory.Length, array.Length); + Assert.IsTrue(Unsafe.AreSame(ref array[0, 0], ref memory.Span[0])); } [TestCategory("Memory2DT")] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs index 0e360a00419..4c23c7bb6bc 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -279,8 +280,9 @@ public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_1() bool success = memory2d.TryGetMemory(out ReadOnlyMemory memory); - Assert.IsFalse(success); - Assert.IsTrue(memory.IsEmpty); + Assert.IsTrue(success); + Assert.AreEqual(memory.Length, array.Length); + Assert.IsTrue(Unsafe.AreSame(ref array[0, 0], ref Unsafe.AsRef(memory.Span[0]))); } [TestCategory("ReadOnlyMemory2DT")] From 6ddb4ee05ea1af9d3adf643fb5b39ef866a47ee0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 9 Oct 2020 19:55:57 +0200 Subject: [PATCH 125/200] Fixed bug in unit tests on UWP --- .../Memory/Test_Memory2D{T}.cs | 7 +++++++ .../Memory/Test_ReadOnlyMemory2D{T}.cs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs index 2d566d610cf..d1e8e1a0ba5 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs @@ -4,8 +4,10 @@ using System; using System.Diagnostics.CodeAnalysis; +#if !WINDOWS_UWP using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Extensions; +#endif using Microsoft.Toolkit.HighPerformance.Memory; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -280,9 +282,14 @@ public void Test_Memory2DT_TryGetMemory_1() bool success = memory2d.TryGetMemory(out Memory memory); +#if WINDOWS_UWP + Assert.IsFalse(success); + Assert.IsTrue(memory.IsEmpty); +#else Assert.IsTrue(success); Assert.AreEqual(memory.Length, array.Length); Assert.IsTrue(Unsafe.AreSame(ref array[0, 0], ref memory.Span[0])); +#endif } [TestCategory("Memory2DT")] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs index 4c23c7bb6bc..0ef196410e2 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs @@ -4,8 +4,10 @@ using System; using System.Diagnostics.CodeAnalysis; +#if !WINDOWS_UWP using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Extensions; +#endif using Microsoft.Toolkit.HighPerformance.Memory; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -280,9 +282,14 @@ public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_1() bool success = memory2d.TryGetMemory(out ReadOnlyMemory memory); +#if WINDOWS_UWP + Assert.IsFalse(success); + Assert.IsTrue(memory.IsEmpty); +#else Assert.IsTrue(success); Assert.AreEqual(memory.Length, array.Length); Assert.IsTrue(Unsafe.AreSame(ref array[0, 0], ref Unsafe.AsRef(memory.Span[0]))); +#endif } [TestCategory("ReadOnlyMemory2DT")] From c4c92f6d6219aa89d2e057f56234dc624e920c79 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 10 Oct 2020 14:06:39 +0200 Subject: [PATCH 126/200] Improved array indexing extensions in portable version --- .../Extensions/ArrayExtensions.1D.cs | 27 ++++++----------- .../Extensions/ArrayExtensions.2D.cs | 27 +++++++---------- .../Extensions/ArrayExtensions.3D.cs | 29 ++++++++----------- .../Helpers/Internals/RuntimeHelpers.cs | 29 +++++++++++++++++++ 4 files changed, 61 insertions(+), 51 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs index f43ddea43dc..9aa1dfd8ef5 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs @@ -10,6 +10,9 @@ #endif using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; +#if !NETCORE_RUNTIME +using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; +#endif namespace Microsoft.Toolkit.HighPerformance.Extensions { @@ -35,20 +38,9 @@ public static ref T DangerousGetReference(this T[] array) return ref r0; #else -#pragma warning disable SA1131 // Inverted comparison to remove JIT bounds check - // Checking the length of the array like so allows the JIT - // to skip its own bounds check, which results in the element - // access below to be executed without branches. - if (0u < (uint)array.Length) - { - return ref array[0]; - } + IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset(); - unsafe - { - return ref Unsafe.AsRef(null); - } -#pragma warning restore SA1131 + return ref array.DangerousGetObjectDataReferenceAt(offset); #endif } @@ -71,12 +63,11 @@ public static unsafe ref T DangerousGetReferenceAt(this T[] array, int i) return ref ri; #else - if ((uint)i < (uint)array.Length) - { - return ref array[i]; - } + IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset(); + ref T r0 = ref array.DangerousGetObjectDataReferenceAt(offset); + ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i); - return ref Unsafe.AsRef(null); + return ref ri; #endif } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index a19348981b8..7175d6c91dc 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -63,29 +63,24 @@ public static ref T DangerousGetReference(this T[,] array) /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T DangerousGetReferenceAt(this T[,] array, int i, int j) + public static unsafe 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, (IntPtr)(void*)(uint)offset); - unsafe - { - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); - - return ref ri; - } + return ref ri; #else - if ((uint)i < (uint)array.GetLength(0) && - (uint)j < (uint)array.GetLength(1)) - { - return ref array[i, j]; - } - - IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset(); - - return ref array.DangerousGetObjectDataReferenceAt(offset); + int + width = array.GetLength(1), + index = (i * width) + j; + IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset(); + ref T r0 = ref array.DangerousGetObjectDataReferenceAt(offset); + ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); + + return ref ri; #endif } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index d5938348287..cab6b3d5b81 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -63,30 +63,25 @@ public static ref T DangerousGetReference(this T[,,] array) /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T DangerousGetReferenceAt(this T[,,] array, int i, int j, int k) + public static unsafe ref T DangerousGetReferenceAt(this T[,,] array, int i, int j, int k) { #if NETCORE_RUNTIME var arrayData = Unsafe.As(array); int offset = (i * arrayData.Height * arrayData.Width) + (j * arrayData.Width) + k; ref T r0 = ref Unsafe.As(ref arrayData.Data); + ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); - unsafe - { - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); - - return ref ri; - } + return ref ri; #else - if ((uint)i < (uint)array.GetLength(0) && - (uint)j < (uint)array.GetLength(1) && - (uint)k < (uint)array.GetLength(2)) - { - return ref array[i, j, k]; - } - - IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset(); - - return ref array.DangerousGetObjectDataReferenceAt(offset); + int + height = array.GetLength(1), + width = array.GetLength(2), + index = (i * height * width) + (j * width) + j; + IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset(); + ref T r0 = ref array.DangerousGetObjectDataReferenceAt(offset); + ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); + + return ref ri; #endif } diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs index 31b8f755bfd..1cc247961f2 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs @@ -22,6 +22,18 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals /// internal static class RuntimeHelpers { + /// + /// Gets the byte offset to the first element in a SZ array. + /// + /// The type of values in the array. + /// The byte offset to the first element in a SZ array. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IntPtr GetArrayDataByteOffset() + { + return TypeInfo.ArrayDataByteOffset; + } + /// /// Gets the byte offset to the first element in a 2D array. /// @@ -160,6 +172,11 @@ private static bool IsReferenceOrContainsReferences(Type type) /// The type to load info for. private static class TypeInfo { + /// + /// The byte offset to the first element in a SZ array. + /// + public static readonly IntPtr ArrayDataByteOffset = MeasureArrayDataByteOffset(); + /// /// The byte offset to the first element in a 2D array. /// @@ -177,6 +194,18 @@ private static class TypeInfo public static readonly bool IsReferenceOrContainsReferences = IsReferenceOrContainsReferences(typeof(T)); #endif + /// + /// Computes the value for . + /// + /// The value of for the current runtime. + [Pure] + private static IntPtr MeasureArrayDataByteOffset() + { + var array = new T[1]; + + return array.DangerousGetObjectDataByteOffset(ref array[0]); + } + /// /// Computes the value for . /// From ee4f26b355a85b64874c0027958048222b9ca7b8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 10 Oct 2020 14:24:15 +0200 Subject: [PATCH 127/200] Minor code tweak --- Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 9d67feee0bc..cf5fbdcce51 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -15,6 +15,7 @@ using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory.Internals; using Microsoft.Toolkit.HighPerformance.Memory.Views; +using static Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; namespace Microsoft.Toolkit.HighPerformance.Memory { @@ -161,7 +162,7 @@ public Memory2D(T[,]? array) } this.instance = array; - this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(0, 0)); + this.offset = GetArray2DDataByteOffset(); this.height = array.GetLength(0); this.width = array.GetLength(1); this.pitch = 0; From 29ca850b1b36750f34ae4599142af0b0f3bfad59 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 10 Oct 2020 18:28:12 +0200 Subject: [PATCH 128/200] Fixed some catastrophic typos --- .../Extensions/ArrayExtensions.2D.cs | 2 +- .../Extensions/ArrayExtensions.3D.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 7175d6c91dc..44332901161 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -76,7 +76,7 @@ public static unsafe ref T DangerousGetReferenceAt(this T[,] array, int i, in int width = array.GetLength(1), index = (i * width) + j; - IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset(); + IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset(); ref T r0 = ref array.DangerousGetObjectDataReferenceAt(offset); ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index cab6b3d5b81..943c3958fd6 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -76,8 +76,8 @@ public static unsafe ref T DangerousGetReferenceAt(this T[,,] array, int i, i int height = array.GetLength(1), width = array.GetLength(2), - index = (i * height * width) + (j * width) + j; - IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset(); + index = (i * height * width) + (j * width) + k; + IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset(); ref T r0 = ref array.DangerousGetObjectDataReferenceAt(offset); ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); From b265679a2e74f188da98558c4de7484c68d54625 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 11 Oct 2020 15:28:55 +0200 Subject: [PATCH 129/200] Added missing array extensions --- .../Extensions/ArrayExtensions.2D.cs | 45 +++++++++++++++++- .../Extensions/ArrayExtensions.3D.cs | 47 +++++++++++++++++-- 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 44332901161..1e295a64ae4 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -323,12 +323,20 @@ public static Memory2D AsMemory2D(this T[,] array, int row, int column, in /// 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 . - /// The returned value shouldn't be used directly: use this extension in a loop. + /// A with the items from the target row within . + /// Thrown when doesn't match . + /// Thrown when is invalid. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span GetRowSpan(this T[,] array, int row) { + if (array.IsCovariant()) + { + static void Throw() => throw new ArrayTypeMismatchException(); + + Throw(); + } + if ((uint)row >= (uint)array.GetLength(0)) { static void Throw() => throw new ArgumentOutOfRangeException(nameof(row)); @@ -341,6 +349,39 @@ public static Span GetRowSpan(this T[,] array, int row) return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1)); } + /// + /// 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 . + /// Thrown when doesn't match . + /// Thrown when is invalid. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory GetRowMemory(this T[,] array, int row) + { + if (array.IsCovariant()) + { + static void Throw() => throw new ArrayTypeMismatchException(); + + Throw(); + } + + if ((uint)row >= (uint)array.GetLength(0)) + { + static void Throw() => throw new ArgumentOutOfRangeException(nameof(row)); + + Throw(); + } + + ref T r0 = ref array.DangerousGetReferenceAt(row, 0); + IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0); + + return new RawObjectMemoryManager(array, offset, array.GetLength(1)).Memory; + } + /// /// Creates a new over an input 2D array. /// diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index 943c3958fd6..dce32e35a14 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -155,15 +155,20 @@ public static Span AsSpan(this T[,,] array) /// The type of elements in the input 3D array instance. /// The given 3D array to wrap. /// The target layer to map within . - /// - /// Thrown when doesn't match . - /// - /// Thrown when either is invalid. + /// Thrown when doesn't match . + /// Thrown when is invalid. /// A instance wrapping the target layer within . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span GetLayerSpan(this T[,,] array, int depth) { + if (array.IsCovariant()) + { + static void Throw() => throw new ArrayTypeMismatchException(); + + Throw(); + } + if ((uint)depth >= (uint)array.GetLength(0)) { static void Throw() => throw new ArgumentOutOfRangeException(nameof(depth)); @@ -176,6 +181,40 @@ public static Span GetLayerSpan(this T[,,] array, int depth) return MemoryMarshal.CreateSpan(ref r0, length); } + + /// + /// Creates a new instance of the struct wrapping a layer in a 3D array. + /// + /// The type of elements in the input 3D array instance. + /// The given 3D array to wrap. + /// The target layer to map within . + /// Thrown when doesn't match . + /// Thrown when is invalid. + /// A instance wrapping the target layer within . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory GetLayerMemory(this T[,,] array, int depth) + { + if (array.IsCovariant()) + { + static void Throw() => throw new ArrayTypeMismatchException(); + + Throw(); + } + + if ((uint)depth >= (uint)array.GetLength(0)) + { + static void Throw() => throw new ArgumentOutOfRangeException(nameof(depth)); + + Throw(); + } + + ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0); + IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0); + int length = array.GetLength(1) * array.GetLength(2); + + return new RawObjectMemoryManager(array, offset, length).Memory; + } #endif /// From 3b25d76d77537fa6a9baa5a415355f3b32ac4e1b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 11 Oct 2020 15:30:39 +0200 Subject: [PATCH 130/200] Bug fixes and speed improvement to T[].IsCovariant --- .../Extensions/ArrayExtensions.1D.cs | 14 +------------- .../Extensions/ArrayExtensions.2D.cs | 11 +---------- .../Extensions/ArrayExtensions.3D.cs | 11 +---------- 3 files changed, 3 insertions(+), 33 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs index 9aa1dfd8ef5..fc54dfb54b6 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs @@ -192,19 +192,7 @@ public static unsafe int GetDjb2HashCode(this T[] array) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsCovariant(this T[] array) { - return -#pragma warning disable SA1003 // Whitespace before ! operator -#if NETSTANDARD1_4 - // On .NET Standard 1.4 we use GetTypeInfo to get the info we need - // on the current type T. We only do this step when absolutely necessary - // as it incurs in more overhead. Additionally, this pattern is recognized - // as a JIT intrinsics on .NET and results in a much more efficient codegen. - !System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(T)).IsValueType && -#else - !typeof(T).IsValueType && -#endif -#pragma warning restore SA1003 - array.GetType() != typeof(T[]); + return default(T) is null && array.GetType() != typeof(T[]); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 1e295a64ae4..fe6323f8104 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -480,16 +480,7 @@ public static unsafe int GetDjb2HashCode(this T[,] array) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsCovariant(this T[,] array) { - return -#pragma warning disable SA1003 // Whitespace before ! operator -#if NETSTANDARD1_4 - - !System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(T)).IsValueType && -#else - !typeof(T).IsValueType && -#endif -#pragma warning restore SA1003 - array.GetType() != typeof(T[]); + return default(T) is null && array.GetType() != typeof(T[,]); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index dce32e35a14..df52cbabf24 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -412,16 +412,7 @@ public static int GetDjb2HashCode(this T[,,] array) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsCovariant(this T[,,] array) { - return -#pragma warning disable SA1003 // Whitespace before ! operator -#if NETSTANDARD1_4 - - !System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(T)).IsValueType && -#else - !typeof(T).IsValueType && -#endif -#pragma warning restore SA1003 - array.GetType() != typeof(T[]); + return default(T) is null && array.GetType() != typeof(T[,,]); } } } From c2fe6d70850b23acf3574c7f1543d44e3ed8bfbc Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 12 Oct 2020 12:14:43 +0200 Subject: [PATCH 131/200] Improved validation in array extensions --- .../Extensions/ArrayExtensions.2D.cs | 68 ++++++++++++++----- .../Extensions/ArrayExtensions.3D.cs | 60 +++++++++------- .../Memory/Internals/ThrowHelper.cs | 2 +- 3 files changed, 87 insertions(+), 43 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index fe6323f8104..9d1dabcb99e 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -168,13 +168,16 @@ public static void Fill(this T[,] array, T value, int row, int column, int wi [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RefEnumerable GetRow(this T[,] array, int row) { + if (array.IsCovariant()) + { + ThrowArrayTypeMismatchException(); + } + int height = array.GetLength(0); if ((uint)row >= (uint)height) { - static void Throw() => throw new ArgumentOutOfRangeException(nameof(row)); - - Throw(); + ThrowArgumentOutOfRangeExceptionForRow(); } int width = array.GetLength(1); @@ -219,13 +222,16 @@ public static RefEnumerable GetRow(this T[,] array, int row) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RefEnumerable GetColumn(this T[,] array, int column) { + if (array.IsCovariant()) + { + ThrowArrayTypeMismatchException(); + } + int width = array.GetLength(1); if ((uint)column >= (uint)width) { - static void Throw() => throw new ArgumentOutOfRangeException(nameof(column)); - - Throw(); + ThrowArgumentOutOfRangeExceptionForColumn(); } int height = array.GetLength(0); @@ -332,16 +338,12 @@ public static Span GetRowSpan(this T[,] array, int row) { if (array.IsCovariant()) { - static void Throw() => throw new ArrayTypeMismatchException(); - - Throw(); + ThrowArrayTypeMismatchException(); } if ((uint)row >= (uint)array.GetLength(0)) { - static void Throw() => throw new ArgumentOutOfRangeException(nameof(row)); - - Throw(); + ThrowArgumentOutOfRangeExceptionForRow(); } ref T r0 = ref array.DangerousGetReferenceAt(row, 0); @@ -364,16 +366,12 @@ public static Memory GetRowMemory(this T[,] array, int row) { if (array.IsCovariant()) { - static void Throw() => throw new ArrayTypeMismatchException(); - - Throw(); + ThrowArrayTypeMismatchException(); } if ((uint)row >= (uint)array.GetLength(0)) { - static void Throw() => throw new ArgumentOutOfRangeException(nameof(row)); - - Throw(); + ThrowArgumentOutOfRangeExceptionForRow(); } ref T r0 = ref array.DangerousGetReferenceAt(row, 0); @@ -392,6 +390,11 @@ public static Memory GetRowMemory(this T[,] array, int row) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Memory AsMemory(this T[,] array) { + if (array.IsCovariant()) + { + ThrowArrayTypeMismatchException(); + } + return new RawObjectMemoryManager(array, RuntimeHelpers.GetArray2DDataByteOffset(), array.Length).Memory; } @@ -405,6 +408,11 @@ public static Memory AsMemory(this T[,] array) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span AsSpan(this T[,] array) { + if (array.IsCovariant()) + { + ThrowArrayTypeMismatchException(); + } + #if NETCORE_RUNTIME var arrayData = Unsafe.As(array); @@ -482,5 +490,29 @@ public static bool IsCovariant(this T[,] array) { return default(T) is null && array.GetType() != typeof(T[,]); } + + /// + /// Throws an when using an array of an invalid type. + /// + private static void ThrowArrayTypeMismatchException() + { + throw new ArrayTypeMismatchException("The given array doesn't match the specified type T"); + } + + /// + /// Throws an when the "row" parameter is invalid. + /// + public static void ThrowArgumentOutOfRangeExceptionForRow() + { + throw new ArgumentOutOfRangeException("row"); + } + + /// + /// Throws an when the "column" parameter is invalid. + /// + public static void ThrowArgumentOutOfRangeExceptionForColumn() + { + throw new ArgumentOutOfRangeException("column"); + } } } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index df52cbabf24..86ccb85158c 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -117,6 +117,11 @@ private sealed class RawArray3DData [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Memory AsMemory(this T[,,] array) { + if (array.IsCovariant()) + { + ThrowArrayTypeMismatchException(); + } + return new RawObjectMemoryManager(array, RuntimeHelpers.GetArray3DDataByteOffset(), array.Length).Memory; } @@ -130,6 +135,11 @@ public static Memory AsMemory(this T[,,] array) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span AsSpan(this T[,,] array) { + if (array.IsCovariant()) + { + ThrowArrayTypeMismatchException(); + } + #if NETCORE_RUNTIME var arrayData = Unsafe.As(array); @@ -164,16 +174,12 @@ public static Span GetLayerSpan(this T[,,] array, int depth) { if (array.IsCovariant()) { - static void Throw() => throw new ArrayTypeMismatchException(); - - Throw(); + ThrowArrayTypeMismatchException(); } if ((uint)depth >= (uint)array.GetLength(0)) { - static void Throw() => throw new ArgumentOutOfRangeException(nameof(depth)); - - Throw(); + ThrowArgumentOutOfRangeExceptionForDepth(); } ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0); @@ -197,16 +203,12 @@ public static Memory GetLayerMemory(this T[,,] array, int depth) { if (array.IsCovariant()) { - static void Throw() => throw new ArrayTypeMismatchException(); - - Throw(); + ThrowArrayTypeMismatchException(); } if ((uint)depth >= (uint)array.GetLength(0)) { - static void Throw() => throw new ArgumentOutOfRangeException(nameof(depth)); - - Throw(); + ThrowArgumentOutOfRangeExceptionForDepth(); } ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0); @@ -231,22 +233,23 @@ public static Memory GetLayerMemory(this T[,,] array, int depth) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RefEnumerable GetRow(this T[,,] array, int depth, int row) { + if (array.IsCovariant()) + { + ThrowArrayTypeMismatchException(); + } + int layers = array.GetLength(0), height = array.GetLength(1); if ((uint)depth >= (uint)layers) { - static void Throw() => throw new ArgumentOutOfRangeException(nameof(depth)); - - Throw(); + ThrowArgumentOutOfRangeExceptionForDepth(); } if ((uint)row >= (uint)height) { - static void Throw() => throw new ArgumentOutOfRangeException(nameof(row)); - - Throw(); + ThrowArgumentOutOfRangeExceptionForRow(); } int width = array.GetLength(2); @@ -297,22 +300,23 @@ public static RefEnumerable GetRow(this T[,,] array, int depth, int row) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RefEnumerable GetColumn(this T[,,] array, int depth, int column) { + if (array.IsCovariant()) + { + ThrowArrayTypeMismatchException(); + } + int layers = array.GetLength(0), width = array.GetLength(2); if ((uint)depth >= (uint)layers) { - static void Throw() => throw new ArgumentOutOfRangeException(nameof(depth)); - - Throw(); + ThrowArgumentOutOfRangeExceptionForDepth(); } if ((uint)column >= (uint)width) { - static void Throw() => throw new ArgumentOutOfRangeException(nameof(column)); - - Throw(); + ThrowArgumentOutOfRangeExceptionForColumn(); } int height = array.GetLength(1); @@ -414,5 +418,13 @@ public static bool IsCovariant(this T[,,] array) { return default(T) is null && array.GetType() != typeof(T[,,]); } + + /// + /// Throws an when the "depth" parameter is invalid. + /// + public static void ThrowArgumentOutOfRangeExceptionForDepth() + { + throw new ArgumentOutOfRangeException("depth"); + } } } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs b/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs index 72dbac3899c..4f372df3c1b 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Internals/ThrowHelper.cs @@ -32,7 +32,7 @@ public static void ThrowArgumentExceptionForDestinationTooShort() /// public static void ThrowArrayTypeMismatchException() { - throw new ArrayTypeMismatchException("The given array doesn't match the specified type "); + throw new ArrayTypeMismatchException("The given array doesn't match the specified type T"); } /// From d998f26f0fe5c235229a57d1fa7de04afb07508a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 12 Oct 2020 14:52:35 +0200 Subject: [PATCH 132/200] Minor code refactoring --- .../Extensions/ArrayExtensions.2D.cs | 19 ++----------------- .../Extensions/ArrayExtensions.3D.cs | 11 ++--------- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 9d1dabcb99e..4cac3874f00 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -413,21 +413,6 @@ public static Span AsSpan(this T[,] array) ThrowArrayTypeMismatchException(); } -#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; if (length == 0) @@ -435,8 +420,8 @@ public static Span AsSpan(this T[,] array) return default; } - ref T r0 = ref array[0, 0]; -#endif + ref T r0 = ref array.DangerousGetReference(); + return MemoryMarshal.CreateSpan(ref r0, length); } #endif diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index 86ccb85158c..2ea3269c75c 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -140,13 +140,6 @@ public static Span AsSpan(this T[,,] array) ThrowArrayTypeMismatchException(); } -#if NETCORE_RUNTIME - var arrayData = Unsafe.As(array); - - // See comments for this in the 2D overload - int length = checked((int)Unsafe.As(ref arrayData.Length)); - ref T r0 = ref Unsafe.As(ref arrayData.Data); -#else int length = array.Length; if (length == 0) @@ -154,8 +147,8 @@ public static Span AsSpan(this T[,,] array) return default; } - ref T r0 = ref array[0, 0, 0]; -#endif + ref T r0 = ref array.DangerousGetReference(); + return MemoryMarshal.CreateSpan(ref r0, length); } From b0e7df6d543afc27f247ebf4242dcd78b23028af Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 12 Oct 2020 18:54:29 +0200 Subject: [PATCH 133/200] Improved unit test coverage --- .../Memory/Test_Span2D{T}.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs index a6825fc2344..2ab310ba19b 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -271,6 +271,27 @@ public void Test_Span2DT_FillAndClear_1() Assert.IsTrue(array.Cast().All(n => n == 0)); } + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_Fill_Empty() + { + int[,] array = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + Span2D span2d = new Span2D(array, 0, 0, 0, 0); + + span2d.Fill(42); + + CollectionAssert.AreEqual(array, array); + + span2d.Clear(); + + CollectionAssert.AreEqual(array, array); + } + [TestCategory("Span2DT")] [TestMethod] public void Test_Span2DT_FillAndClear_2() @@ -304,6 +325,17 @@ public void Test_Span2DT_FillAndClear_2() CollectionAssert.AreEqual(array, cleared); } + [TestCategory("Span2DT")] + [TestMethod] + public void Test_Span2DT_CopyTo_Empty() + { + Span2D span2d = Span2D.Empty; + + int[] target = new int[0]; + + span2d.CopyTo(target); + } + [TestCategory("Span2DT")] [TestMethod] public void Test_Span2DT_CopyTo_1() @@ -346,6 +378,7 @@ public void Test_Span2DT_CopyTo_2() CollectionAssert.AreEqual(target, expected); Assert.ThrowsException(() => new Span2D(array).CopyTo(Span.Empty)); + Assert.ThrowsException(() => new Span2D(array, 0, 1, 2, 2).CopyTo(Span.Empty)); } [TestCategory("Span2DT")] From 000ed71349527ac4053f9f025f4e431615774067 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 14 Oct 2020 17:10:32 +0200 Subject: [PATCH 134/200] Tweaked nullability annotations to follow the BCL --- .../Extensions/ArrayExtensions.2D.cs | 26 +++++++++-------- .../Extensions/ArrayExtensions.3D.cs | 20 +++++++------ .../Memory/Memory2D{T}.cs | 21 ++++++++------ .../Memory/ReadOnlyMemory2D{T}.cs | 28 +++++++++---------- .../Memory/ReadOnlySpan2D{T}.cs | 14 +++++++++- .../Memory/Span2D{T}.cs | 21 ++++++++------ 6 files changed, 79 insertions(+), 51 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 4cac3874f00..00432a272c2 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -280,7 +280,7 @@ public static Span2D AsSpan2D(this T[,]? array) /// A instance with the values of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span2D AsSpan2D(this T[,] array, int row, int column, int height, int width) + public static Span2D AsSpan2D(this T[,]? array, int row, int column, int height, int width) { return new Span2D(array, row, column, height, width); } @@ -293,7 +293,7 @@ public static Span2D AsSpan2D(this T[,] array, int row, int column, int he /// A instance with the values of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Memory2D AsMemory2D(this T[,] array) + public static Memory2D AsMemory2D(this T[,]? array) { return new Memory2D(array); } @@ -317,7 +317,7 @@ public static Memory2D AsMemory2D(this T[,] array) /// A instance with the values of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Memory2D AsMemory2D(this T[,] array, int row, int column, int height, int width) + public static Memory2D AsMemory2D(this T[,]? array, int row, int column, int height, int width) { return new Memory2D(array, row, column, height, width); } @@ -388,8 +388,13 @@ public static Memory GetRowMemory(this T[,] array, int row) /// A instance with the values of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Memory AsMemory(this T[,] array) + public static Memory AsMemory(this T[,]? array) { + if (array is null) + { + return default; + } + if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); @@ -406,21 +411,20 @@ public static Memory AsMemory(this T[,] array) /// A instance with the values of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span AsSpan(this T[,] array) + public static Span AsSpan(this T[,]? array) { - if (array.IsCovariant()) + if (array is null) { - ThrowArrayTypeMismatchException(); + return default; } - int length = array.Length; - - if (length == 0) + if (array.IsCovariant()) { - return default; + ThrowArrayTypeMismatchException(); } ref T r0 = ref array.DangerousGetReference(); + int length = array.Length; return MemoryMarshal.CreateSpan(ref r0, length); } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index 2ea3269c75c..65e1828dba4 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -115,8 +115,13 @@ private sealed class RawArray3DData /// A instance with the values of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Memory AsMemory(this T[,,] array) + public static Memory AsMemory(this T[,,]? array) { + if (array is null) + { + return default; + } + if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); @@ -133,21 +138,20 @@ public static Memory AsMemory(this T[,,] array) /// A instance with the values of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span AsSpan(this T[,,] array) + public static Span AsSpan(this T[,,]? array) { - if (array.IsCovariant()) + if (array is null) { - ThrowArrayTypeMismatchException(); + return default; } - int length = array.Length; - - if (length == 0) + if (array.IsCovariant()) { - return default; + ThrowArrayTypeMismatchException(); } ref T r0 = ref array.DangerousGetReference(); + int length = array.Length; return MemoryMarshal.CreateSpan(ref r0, length); } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index cf5fbdcce51..2e90e8d8d59 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -117,13 +117,6 @@ public Memory2D(T[] array, int offset, int height, int width, int pitch) ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } - if (width == 0 || height == 0) - { - this = default; - - return; - } - int remaining = array.Length - offset, area = ((width + pitch) * (height - 1)) + width; @@ -183,8 +176,20 @@ public Memory2D(T[,]? array) /// Thrown when either , or /// are negative or not within the bounds that are valid for . /// - public Memory2D(T[,] array, int row, int column, int height, int width) + public Memory2D(T[,]? array, int row, int column, int height, int width) { + if (array is null) + { + if (row != 0 || column != 0 || height != 0 || width != 0) + { + ThrowHelper.ThrowArgumentException(); + } + + this = default; + + return; + } + if (array.IsCovariant()) { ThrowHelper.ThrowArrayTypeMismatchException(); diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index e640fa7f275..6238dc1fd31 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -103,13 +103,6 @@ public ReadOnlyMemory2D(string text, int offset, int height, int width, int pitc ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } - if (width == 0 || height == 0) - { - this = default; - - return; - } - int remaining = text.Length - offset, area = ((width + pitch) * (height - 1)) + width; @@ -188,13 +181,6 @@ public ReadOnlyMemory2D(T[] array, int offset, int height, int width, int pitch) ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } - if (width == 0 || height == 0) - { - this = default; - - return; - } - int remaining = array.Length - offset, area = ((width + pitch) * (height - 1)) + width; @@ -254,8 +240,20 @@ public ReadOnlyMemory2D(T[,]? array) /// Thrown when either , or /// are negative or not within the bounds that are valid for . /// - public ReadOnlyMemory2D(T[,] array, int row, int column, int height, int width) + public ReadOnlyMemory2D(T[,]? array, int row, int column, int height, int width) { + if (array is null) + { + if (row != 0 || column != 0 || height != 0 || width != 0) + { + ThrowHelper.ThrowArgumentException(); + } + + this = default; + + return; + } + if (array.IsCovariant()) { ThrowHelper.ThrowArrayTypeMismatchException(); diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 9386d4f093e..106ea6ce938 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -271,8 +271,20 @@ public ReadOnlySpan2D(T[,]? array) /// Thrown when either , or /// are negative or not within the bounds that are valid for . /// - public ReadOnlySpan2D(T[,] array, int row, int column, int height, int width) + public ReadOnlySpan2D(T[,]? array, int row, int column, int height, int width) { + if (array is null) + { + if (row != 0 || column != 0 || height != 0 || width != 0) + { + ThrowHelper.ThrowArgumentException(); + } + + this = default; + + return; + } + if (array.IsCovariant()) { ThrowHelper.ThrowArrayTypeMismatchException(); diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index f2085ed250c..d08f89b845e 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -226,13 +226,6 @@ public Span2D(T[] array, int offset, int height, int width, int pitch) ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } - if (width == 0 || height == 0) - { - this = default; - - return; - } - int remaining = array.Length - offset, area = ((width + pitch) * (height - 1)) + width; @@ -300,8 +293,20 @@ public Span2D(T[,]? array) /// Thrown when either , or /// are negative or not within the bounds that are valid for . /// - public Span2D(T[,] array, int row, int column, int height, int width) + public Span2D(T[,]? array, int row, int column, int height, int width) { + if (array is null) + { + if (row != 0 || column != 0 || height != 0 || width != 0) + { + ThrowHelper.ThrowArgumentException(); + } + + this = default; + + return; + } + if (array.IsCovariant()) { ThrowHelper.ThrowArrayTypeMismatchException(); From 21928da943612ec36ef2ce05d23f55832148f356 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 14 Oct 2020 23:33:37 +0200 Subject: [PATCH 135/200] Removed T[,].Fill extension (replaced by Span2D.Fill) --- .../Extensions/ArrayExtensions.2D.cs | 47 ---------------- .../Extensions/Test_ArrayExtensions.2D.cs | 56 +++---------------- 2 files changed, 9 insertions(+), 94 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 00432a272c2..5052fcc0bbb 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics.Contracts; -using System.Drawing; using System.Runtime.CompilerServices; #if SPAN_RUNTIME_SUPPORT using System.Runtime.InteropServices; @@ -109,52 +108,6 @@ private sealed class RawArray2DData } #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. - [Obsolete("Use array.AsSpan2D(...).Fill(...) instead")] - 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. /// diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs index 37582686d25..170f879feee 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs @@ -81,11 +81,11 @@ public void Test_ArrayExtensions_2D_DangerousGetReferenceAt_Index() [TestCategory("ArrayExtensions")] [TestMethod] - public void Test_ArrayExtensions_2D_FillArrayMid() + public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayMid() { bool[,] test = new bool[4, 5]; - test.Fill(true, 1, 1, 3, 2); + test.AsSpan2D(1, 1, 2, 3).Fill(true); var expected = new[,] { @@ -100,12 +100,12 @@ public void Test_ArrayExtensions_2D_FillArrayMid() [TestCategory("ArrayExtensions")] [TestMethod] - public void Test_ArrayExtensions_2D_FillArrayTwice() + public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayTwice() { bool[,] test = new bool[4, 5]; - test.Fill(true, 0, 0, 1, 2); - test.Fill(true, 1, 3, 2, 2); + test.AsSpan2D(0, 0, 2, 1).Fill(true); + test.AsSpan2D(1, 3, 2, 2).Fill(true); var expected = new[,] { @@ -120,30 +120,11 @@ public void Test_ArrayExtensions_2D_FillArrayTwice() [TestCategory("ArrayExtensions")] [TestMethod] - public void Test_ArrayExtensions_2D_FillArrayNegativeSize() + public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayBottomEdgeBoundary() { bool[,] test = new bool[4, 5]; - test.Fill(true, 3, 4, -3, -2); - - var expected = new[,] - { - { false, false, false, false, false }, - { false, false, false, false, false }, - { false, false, false, false, false }, - { false, false, false, false, false }, - }; - - CollectionAssert.AreEqual(expected, test); - } - - [TestCategory("ArrayExtensions")] - [TestMethod] - public void Test_ArrayExtensions_2D_FillArrayBottomEdgeBoundary() - { - bool[,] test = new bool[4, 5]; - - test.Fill(true, 1, 2, 2, 4); + test.AsSpan2D(1, 2, 3, 2).Fill(true); var expected = new[,] { @@ -158,30 +139,11 @@ public void Test_ArrayExtensions_2D_FillArrayBottomEdgeBoundary() [TestCategory("ArrayExtensions")] [TestMethod] - public void Test_ArrayExtensions_2D_FillArrayTopLeftCornerNegativeBoundary() - { - bool[,] test = new bool[4, 5]; - - test.Fill(true, -1, -1, 3, 3); - - var expected = new[,] - { - { true, true, false, false, false }, - { true, true, false, false, false }, - { false, false, false, false, false }, - { false, false, false, false, false }, - }; - - CollectionAssert.AreEqual(expected, test); - } - - [TestCategory("ArrayExtensions")] - [TestMethod] - public void Test_ArrayExtensions_2D_FillArrayBottomRightCornerBoundary() + public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayBottomRightCornerBoundary() { bool[,] test = new bool[5, 4]; - test.Fill(true, 3, 2, 3, 3); + test.AsSpan2D(3, 2, 2, 2).Fill(true); var expected = new[,] { From f9a935c03346dbfc6dd35bb8eff1ba1fc9393653 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 14 Oct 2020 23:55:20 +0200 Subject: [PATCH 136/200] Added missing Range/Index indexers --- .../Memory/Memory2D{T}.cs | 23 +++++++++++ .../Memory/ReadOnlyMemory2D{T}.cs | 23 +++++++++++ .../Memory/ReadOnlySpan2D{T}.cs | 38 +++++++++++++++++++ .../Memory/Span2D{T}.cs | 38 +++++++++++++++++++ 4 files changed, 122 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 2e90e8d8d59..b1a15a60cfd 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -633,6 +633,29 @@ public Span2D Span } } +#if NETSTANDARD2_1_OR_GREATER + /// + /// Slices the current instance with the specified parameters. + /// + /// The target range of rows to select. + /// The target range of columns to select. + /// + /// Thrown when either or are invalid. + /// + /// A new instance representing a slice of the current one. + public Memory2D this[Range rows, Range columns] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + var (row, height) = rows.GetOffsetAndLength(this.height); + var (column, width) = columns.GetOffsetAndLength(this.width); + + return Slice(row, column, height, width); + } + } +#endif + /// /// Slices the current instance with the specified parameters. /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 6238dc1fd31..7aa57c20d78 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -691,6 +691,29 @@ public ReadOnlySpan2D Span } } +#if NETSTANDARD2_1_OR_GREATER + /// + /// Slices the current instance with the specified parameters. + /// + /// The target range of rows to select. + /// The target range of columns to select. + /// + /// Thrown when either or are invalid. + /// + /// A new instance representing a slice of the current one. + public ReadOnlyMemory2D this[Range rows, Range columns] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + var (row, height) = rows.GetOffsetAndLength(this.height); + var (column, width) = columns.GetOffsetAndLength(this.width); + + return Slice(row, column, height, width); + } + } +#endif + /// /// Slices the current instance with the specified parameters. /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 106ea6ce938..93b498e8333 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -521,6 +521,44 @@ public int Width } } +#if NETSTANDARD2_1_OR_GREATER + /// + /// Gets the element at the specified zero-based indices. + /// + /// The target row to get the element from. + /// The target column to get the element from. + /// A reference to the element at the specified indices. + /// + /// Thrown when either or are invalid. + /// + public ref readonly T this[Index i, Index j] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this[i.GetOffset(Height), j.GetOffset(this.width)]; + } + + /// + /// Slices the current instance with the specified parameters. + /// + /// The target range of rows to select. + /// The target range of columns to select. + /// + /// Thrown when either or are invalid. + /// + /// A new instance representing a slice of the current one. + public ReadOnlySpan2D this[Range rows, Range columns] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + var (row, height) = rows.GetOffsetAndLength(Height); + var (column, width) = columns.GetOffsetAndLength(this.width); + + return Slice(row, column, height, width); + } + } +#endif + /// /// Copies the contents of this into a destination instance. /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index d08f89b845e..0226eae0f20 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -543,6 +543,44 @@ public int Width } } +#if NETSTANDARD2_1_OR_GREATER + /// + /// Gets the element at the specified zero-based indices. + /// + /// The target row to get the element from. + /// The target column to get the element from. + /// A reference to the element at the specified indices. + /// + /// Thrown when either or are invalid. + /// + public ref T this[Index i, Index j] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this[i.GetOffset(Height), j.GetOffset(this.width)]; + } + + /// + /// Slices the current instance with the specified parameters. + /// + /// The target range of rows to select. + /// The target range of columns to select. + /// + /// Thrown when either or are invalid. + /// + /// A new instance representing a slice of the current one. + public Span2D this[Range rows, Range columns] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + var (row, height) = rows.GetOffsetAndLength(Height); + var (column, width) = columns.GetOffsetAndLength(this.width); + + return Slice(row, column, height, width); + } + } +#endif + /// /// Clears the contents of the current instance. /// From 3d75e3b263e285f55077af646ee04b2585bd68b5 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 15 Oct 2020 00:55:56 +0200 Subject: [PATCH 137/200] Changed names of Span2D indexer parameters --- .../Memory/ReadOnlySpan2D{T}.cs | 24 +++++++++---------- .../Memory/Span2D{T}.cs | 24 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 93b498e8333..af1dd117cac 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -500,24 +500,24 @@ public int Width /// /// Gets the element at the specified zero-based indices. /// - /// The target row to get the element from. - /// The target column to get the element from. + /// The target row to get the element from. + /// The target column to get the element from. /// A reference to the element at the specified indices. /// - /// Thrown when either or are invalid. + /// Thrown when either or are invalid. /// - public ref readonly T this[int i, int j] + public ref readonly T this[int row, int column] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((uint)i >= (uint)Height || - (uint)j >= (uint)Width) + if ((uint)row >= (uint)Height || + (uint)column >= (uint)Width) { ThrowHelper.ThrowIndexOutOfRangeException(); } - return ref DangerousGetReferenceAt(i, j); + return ref DangerousGetReferenceAt(row, column); } } @@ -525,16 +525,16 @@ public int Width /// /// Gets the element at the specified zero-based indices. /// - /// The target row to get the element from. - /// The target column to get the element from. + /// The target row to get the element from. + /// The target column to get the element from. /// A reference to the element at the specified indices. /// - /// Thrown when either or are invalid. + /// Thrown when either or are invalid. /// - public ref readonly T this[Index i, Index j] + public ref readonly T this[Index row, Index column] { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref this[i.GetOffset(Height), j.GetOffset(this.width)]; + get => ref this[row.GetOffset(Height), column.GetOffset(this.width)]; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 0226eae0f20..579cb456594 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -522,24 +522,24 @@ public int Width /// /// Gets the element at the specified zero-based indices. /// - /// The target row to get the element from. - /// The target column to get the element from. + /// The target row to get the element from. + /// The target column to get the element from. /// A reference to the element at the specified indices. /// - /// Thrown when either or are invalid. + /// Thrown when either or are invalid. /// - public ref T this[int i, int j] + public ref T this[int row, int column] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((uint)i >= (uint)Height || - (uint)j >= (uint)this.width) + if ((uint)row >= (uint)Height || + (uint)column >= (uint)this.width) { ThrowHelper.ThrowIndexOutOfRangeException(); } - return ref DangerousGetReferenceAt(i, j); + return ref DangerousGetReferenceAt(row, column); } } @@ -547,16 +547,16 @@ public int Width /// /// Gets the element at the specified zero-based indices. /// - /// The target row to get the element from. - /// The target column to get the element from. + /// The target row to get the element from. + /// The target column to get the element from. /// A reference to the element at the specified indices. /// - /// Thrown when either or are invalid. + /// Thrown when either or are invalid. /// - public ref T this[Index i, Index j] + public ref T this[Index row, Index column] { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref this[i.GetOffset(Height), j.GetOffset(this.width)]; + get => ref this[row.GetOffset(Height), column.GetOffset(this.width)]; } /// From 88ef25cd06500bd8d9f7f61341d5e15ea2f1bd0f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 15 Oct 2020 20:03:42 +0200 Subject: [PATCH 138/200] Added [ReadOnly]RefEnumerable helper methods --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 67 ++++++++++ .../Enumerables/RefEnumerable{T}.cs | 126 +++++++++++++++++- 2 files changed, 192 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index 8dbcbede1e1..eb1138f11bb 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -137,6 +137,65 @@ public readonly ref readonly T Current } } + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public unsafe void CopyTo(Span destination) + { +#if SPAN_RUNTIME_SUPPORT + if (this.step == 1) + { + this.span.CopyTo(destination); + + return; + } + + ref T sourceRef = ref this.span.DangerousGetReference(); + int length = this.span.Length; +#else + ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); + int length = this.length; +#endif + if ((uint)destination.Length < (uint)(length / this.step)) + { + ThrowArgumentExceptionForDestinationTooShort(); + } + + ref T destinationRef = ref destination.DangerousGetReference(); + + for (int i = 0; i < length; i += this.step) + { + Unsafe.Add(ref destinationRef, (IntPtr)(void*)(uint)i) = Unsafe.Add(ref sourceRef, (IntPtr)(void*)(uint)i); + } + } + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operation was successful. + public bool TryCopyTo(Span destination) + { +#if SPAN_RUNTIME_SUPPORT + int length = this.span.Length; +#else + int length = this.length; +#endif + + if (destination.Length >= length / this.step) + { + CopyTo(destination); + + return true; + } + + return false; + } + /// [Pure] public readonly unsafe T[] ToArray() @@ -175,5 +234,13 @@ public readonly unsafe T[] ToArray() return array; } + + /// + /// Throws an when the target span is too short. + /// + private static void ThrowArgumentExceptionForDestinationTooShort() + { + throw new ArgumentException("The target span is too short to copy all the current items to"); + } } } diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 2d8fec96359..349f1c474fc 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -137,6 +137,123 @@ public readonly ref T Current } } + /// + /// Clears the contents of the current instance. + /// + public readonly unsafe void Clear() + { +#if SPAN_RUNTIME_SUPPORT + // Fast path for contiguous items + if (this.step == 1) + { + this.span.Clear(); + + return; + } + + ref T r0 = ref this.span.DangerousGetReference(); + int length = this.span.Length; +#else + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); + int length = this.length; +#endif + + for (int i = 0; i < length; i += this.step) + { + Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i) = default!; + } + } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public unsafe void CopyTo(Span destination) + { +#if SPAN_RUNTIME_SUPPORT + if (this.step == 1) + { + this.span.CopyTo(destination); + + return; + } + + ref T sourceRef = ref this.span.DangerousGetReference(); + int length = this.span.Length; +#else + ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); + int length = this.length; +#endif + if ((uint)destination.Length < (uint)(length / this.step)) + { + ThrowArgumentExceptionForDestinationTooShort(); + } + + ref T destinationRef = ref destination.DangerousGetReference(); + + for (int i = 0; i < length; i += this.step) + { + Unsafe.Add(ref destinationRef, (IntPtr)(void*)(uint)i) = Unsafe.Add(ref sourceRef, (IntPtr)(void*)(uint)i); + } + } + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operation was successful. + public bool TryCopyTo(Span destination) + { +#if SPAN_RUNTIME_SUPPORT + int length = this.span.Length; +#else + int length = this.length; +#endif + + if (destination.Length >= length / this.step) + { + CopyTo(destination); + + return true; + } + + return false; + } + + /// + /// Fills the elements of this with a specified value. + /// + /// The value to assign to each element of the instance. + /// + /// This method will always return the whole sequence from the start, ignoring the + /// current position in case the sequence has already been enumerated in part. + /// + public readonly unsafe void Fill(T value) + { +#if SPAN_RUNTIME_SUPPORT + if (this.step == 1) + { + this.span.Fill(value); + + return; + } + + ref T r0 = ref this.span.DangerousGetReference(); + int length = this.span.Length; +#else + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); + int length = this.length; +#endif + + for (int i = 0; i < length; i += this.step) + { + Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i) = value; + } + } + /// /// Returns a array with the values in the target row. /// @@ -151,7 +268,6 @@ public readonly ref T Current public readonly unsafe T[] ToArray() { #if SPAN_RUNTIME_SUPPORT - // Fast path for contiguous items if (this.step == 1) { return this.span.ToArray(); @@ -184,5 +300,13 @@ public readonly unsafe T[] ToArray() return array; } + + /// + /// Throws an when the target span is too short. + /// + private static void ThrowArgumentExceptionForDestinationTooShort() + { + throw new ArgumentException("The target span is too short to copy all the current items to"); + } } } From e7738f3e9b42c98a8416c393ee079b3c0af85c91 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 15 Oct 2020 20:06:12 +0200 Subject: [PATCH 139/200] Added missing readonly modifiers --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 4 ++-- .../Enumerables/RefEnumerable{T}.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index eb1138f11bb..e637a3fa7bc 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -144,7 +144,7 @@ public readonly ref readonly T Current /// /// Thrown when is shorter than the source instance. /// - public unsafe void CopyTo(Span destination) + public readonly unsafe void CopyTo(Span destination) { #if SPAN_RUNTIME_SUPPORT if (this.step == 1) @@ -178,7 +178,7 @@ public unsafe void CopyTo(Span destination) /// /// The target of the copy operation. /// Whether or not the operation was successful. - public bool TryCopyTo(Span destination) + public readonly bool TryCopyTo(Span destination) { #if SPAN_RUNTIME_SUPPORT int length = this.span.Length; diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 349f1c474fc..2d3d04b404b 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -171,7 +171,7 @@ public readonly unsafe void Clear() /// /// Thrown when is shorter than the source instance. /// - public unsafe void CopyTo(Span destination) + public readonly unsafe void CopyTo(Span destination) { #if SPAN_RUNTIME_SUPPORT if (this.step == 1) @@ -205,7 +205,7 @@ public unsafe void CopyTo(Span destination) /// /// The target of the copy operation. /// Whether or not the operation was successful. - public bool TryCopyTo(Span destination) + public readonly bool TryCopyTo(Span destination) { #if SPAN_RUNTIME_SUPPORT int length = this.span.Length; From 957cb36b3063c5753f41f348fce126c3944e6ac8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 15 Oct 2020 23:45:37 +0200 Subject: [PATCH 140/200] Codegen improvements in enumerator types --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 15 ++------------- .../Enumerables/ReadOnlySpanEnumerable{T}.cs | 11 +---------- .../Enumerables/RefEnumerable{T}.cs | 15 ++------------- .../Enumerables/SpanEnumerable{T}.cs | 11 +---------- .../Memory/ReadOnlySpan2D{T}.Enumerator.cs | 16 ++++------------ .../Memory/Span2D{T}.Enumerator.cs | 16 ++++------------ 6 files changed, 14 insertions(+), 70 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index e637a3fa7bc..698288442b9 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -99,22 +99,11 @@ internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { - int position = this.position + this.step; - - if ( #if SPAN_RUNTIME_SUPPORT - position < this.span.Length + return (this.position += this.step) < this.span.Length; #else - position < this.length + return (this.position += this.step) < this.length; #endif - ) - { - this.position = position; - - return true; - } - - return false; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs index feba42c8db8..0da385b4651 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs @@ -54,16 +54,7 @@ public ReadOnlySpanEnumerable(ReadOnlySpan span) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { - int newIndex = this.index + 1; - - if (newIndex < this.span.Length) - { - this.index = newIndex; - - return true; - } - - return false; + return ++this.index < this.span.Length; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 2d3d04b404b..fd5efdcc129 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -99,22 +99,11 @@ internal RefEnumerable(object? instance, IntPtr offset, int length, int step) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { - int position = this.position + this.step; - - if ( #if SPAN_RUNTIME_SUPPORT - position < this.span.Length + return (this.position += this.step) < this.span.Length; #else - position < this.length + return (this.position += this.step) < this.length; #endif - ) - { - this.position = position; - - return true; - } - - return false; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs index 9811c24770a..ba39222fba9 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs @@ -54,16 +54,7 @@ public SpanEnumerable(Span span) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { - int newIndex = this.index + 1; - - if (newIndex < this.span.Length) - { - this.index = newIndex; - - return true; - } - - return false; + return ++this.index < this.span.Length; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs index 14f472ef3c4..5516c4bb574 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs @@ -169,21 +169,13 @@ public bool MoveNext() // We reached the end of a row and there is at least // another row available: wrap to a new line and continue. - if ( + this.x = 0; + #if SPAN_RUNTIME_SUPPORT - this.y < (this.span.Length - 1) + return this.y++ < (this.span.Length - 1); #else - this.y < this.height - 1 + return this.y++ < this.height - 1; #endif - ) - { - this.x = 0; - this.y++; - - return true; - } - - return false; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index 6178674e4fd..c6d07ed2136 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -169,21 +169,13 @@ public bool MoveNext() // We reached the end of a row and there is at least // another row available: wrap to a new line and continue. - if ( + this.x = 0; + #if SPAN_RUNTIME_SUPPORT - this.y < (this.span.Length - 1) + return this.y++ < (this.span.Length - 1); #else - this.y < this.height - 1 + return this.y++ < this.height - 1; #endif - ) - { - this.x = 0; - this.y++; - - return true; - } - - return false; } /// From 8addab05e4afd44a93e2d5d52d6db9958a70bd8e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 16 Oct 2020 15:02:40 +0200 Subject: [PATCH 141/200] Bug fixes in [ReadOnly]RefEnumerable.CopyTo --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 4 ++-- .../Enumerables/RefEnumerable{T}.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index 698288442b9..9c9bd8db320 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -156,9 +156,9 @@ public readonly unsafe void CopyTo(Span destination) ref T destinationRef = ref destination.DangerousGetReference(); - for (int i = 0; i < length; i += this.step) + for (int i = 0, j = 0; i < length; i += this.step, j++) { - Unsafe.Add(ref destinationRef, (IntPtr)(void*)(uint)i) = Unsafe.Add(ref sourceRef, (IntPtr)(void*)(uint)i); + Unsafe.Add(ref destinationRef, (IntPtr)(void*)(uint)j) = Unsafe.Add(ref sourceRef, (IntPtr)(void*)(uint)i); } } diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index fd5efdcc129..3157bf9dc3d 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -183,9 +183,9 @@ public readonly unsafe void CopyTo(Span destination) ref T destinationRef = ref destination.DangerousGetReference(); - for (int i = 0; i < length; i += this.step) + for (int i = 0, j = 0; i < length; i += this.step, j++) { - Unsafe.Add(ref destinationRef, (IntPtr)(void*)(uint)i) = Unsafe.Add(ref sourceRef, (IntPtr)(void*)(uint)i); + Unsafe.Add(ref destinationRef, (IntPtr)(void*)(uint)j) = Unsafe.Add(ref sourceRef, (IntPtr)(void*)(uint)i); } } From 212717dbcad90cd3f3e21eae1d67c638675c0d45 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 16 Oct 2020 15:03:05 +0200 Subject: [PATCH 142/200] Added [ReadOnly]RefEnumerable tests --- .../Extensions/Test_ArrayExtensions.2D.cs | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs index 170f879feee..d1e098c1052 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance.Memory; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace UnitTests.HighPerformance.Extensions @@ -192,6 +193,134 @@ public void Test_ArrayExtensions_2D_GetRow_Empty() Assert.ThrowsException(() => array.GetRow(0).ToArray()); } + [TestCategory("ArrayExtensions")] + [TestMethod] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] + [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] + public void Test_ArrayExtensions_2D_GetRowOrColumn_Helpers() + { + int[,] array = + { + { 1, 2, 3, 4 }, + { 5, 6, 7, 8 }, + { 9, 10, 11, 12 }, + { 13, 14, 15, 16 } + }; + + array.AsSpan2D(1, 1, 3, 3).GetRow(0).Clear(); + + int[,] expected = + { + { 1, 2, 3, 4 }, + { 5, 0, 0, 0 }, + { 9, 10, 11, 12 }, + { 13, 14, 15, 16 } + }; + + CollectionAssert.AreEqual(array, expected); + + array.GetColumn(2).Fill(42); + + expected = new[,] + { + { 1, 2, 42, 4 }, + { 5, 0, 42, 0 }, + { 9, 10, 42, 12 }, + { 13, 14, 42, 16 } + }; + + CollectionAssert.AreEqual(array, expected); + + int[] copy = new int[4]; + + array.GetRow(2).CopyTo(copy); + + int[] result = { 9, 10, 42, 12 }; + + CollectionAssert.AreEqual(copy, result); + + array.GetColumn(1).CopyTo(copy); + + result = new[] { 2, 0, 10, 14 }; + + CollectionAssert.AreEqual(copy, result); + + Assert.ThrowsException(() => array.GetRow(0).CopyTo(default)); + + Assert.ThrowsException(() => array.GetColumn(0).CopyTo(default)); + + Assert.IsTrue(array.GetRow(2).TryCopyTo(copy)); + + result = new[] { 9, 10, 42, 12 }; + + CollectionAssert.AreEqual(copy, result); + + array.GetRow(2).Fill(99); + + expected = new[,] + { + { 1, 2, 42, 4 }, + { 5, 0, 42, 0 }, + { 99, 99, 99, 99 }, + { 13, 14, 42, 16 } + }; + + CollectionAssert.AreEqual(array, expected); + + array.GetColumn(2).Clear(); + + expected = new[,] + { + { 1, 2, 0, 4 }, + { 5, 0, 0, 0 }, + { 99, 99, 0, 99 }, + { 13, 14, 0, 16 } + }; + + CollectionAssert.AreEqual(array, expected); + } + + [TestCategory("ArrayExtensions")] + [TestMethod] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] + [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] + public void Test_ArrayExtensions_2D_ReadOnlyGetRowOrColumn_Helpers() + { + int[,] array = + { + { 1, 2, 3, 4 }, + { 5, 6, 7, 8 }, + { 9, 10, 11, 12 }, + { 13, 14, 15, 16 } + }; + + ReadOnlySpan2D span2D = array; + + int[] copy = new int[4]; + + span2D.GetRow(2).CopyTo(copy); + + int[] result = { 9, 10, 11, 12 }; + + CollectionAssert.AreEqual(copy, result); + + span2D.GetColumn(1).CopyTo(copy); + + result = new[] { 2, 6, 10, 14 }; + + CollectionAssert.AreEqual(copy, result); + + Assert.ThrowsException(() => ((ReadOnlySpan2D)array).GetRow(0).CopyTo(default)); + + Assert.ThrowsException(() => ((ReadOnlySpan2D)array).GetColumn(0).CopyTo(default)); + + Assert.IsTrue(span2D.GetRow(2).TryCopyTo(copy)); + + result = new[] { 9, 10, 11, 12 }; + + CollectionAssert.AreEqual(copy, result); + } + [TestCategory("ArrayExtensions")] [TestMethod] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] From d7a4ece5a83a55d73aad1eb770f6f3bfdbc0eb15 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 17 Oct 2020 02:31:49 +0200 Subject: [PATCH 143/200] Removed unnecessary APIs --- .../Extensions/ArrayExtensions.3D.cs | 115 ------------------ .../Extensions/Test_ArrayExtensions.3D.cs | 51 -------- 2 files changed, 166 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index 65e1828dba4..b90e74cffb0 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -5,7 +5,6 @@ using System; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; -using Microsoft.Toolkit.HighPerformance.Enumerables; #if SPAN_RUNTIME_SUPPORT using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Buffers.Internals; @@ -216,120 +215,6 @@ public static Memory GetLayerMemory(this T[,,] array, int depth) } #endif - /// - /// Returns a over a row in a given 3D array instance. - /// - /// The type of elements in the input 3D array instance. - /// The input array instance. - /// The target layer to map within . - /// The target row to retrieve (0-based index). - /// A with the items from the target row within . - /// The returned value shouldn't be used directly: use this extension in a loop. - /// Thrown when one of the input parameters is out of range. - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RefEnumerable GetRow(this T[,,] array, int depth, int row) - { - if (array.IsCovariant()) - { - ThrowArrayTypeMismatchException(); - } - - int - layers = array.GetLength(0), - height = array.GetLength(1); - - if ((uint)depth >= (uint)layers) - { - ThrowArgumentOutOfRangeExceptionForDepth(); - } - - if ((uint)row >= (uint)height) - { - ThrowArgumentOutOfRangeExceptionForRow(); - } - - int width = array.GetLength(2); - -#if SPAN_RUNTIME_SUPPORT - ref T r0 = ref array.DangerousGetReferenceAt(depth, row, 0); - - return new RefEnumerable(ref r0, width, 1); -#else - ref T r0 = ref array.DangerousGetReferenceAt(depth, row, 0); - IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0); - - return new RefEnumerable(array, offset, width, 1); -#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 }, - /// { 10, 11, 12 }, - /// } - /// }; - /// - /// foreach (ref int number in matrix.GetColumn(1, 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 layer to map within . - /// 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. - /// Thrown when one of the input parameters is out of range. - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RefEnumerable GetColumn(this T[,,] array, int depth, int column) - { - if (array.IsCovariant()) - { - ThrowArrayTypeMismatchException(); - } - - int - layers = array.GetLength(0), - width = array.GetLength(2); - - if ((uint)depth >= (uint)layers) - { - ThrowArgumentOutOfRangeExceptionForDepth(); - } - - if ((uint)column >= (uint)width) - { - ThrowArgumentOutOfRangeExceptionForColumn(); - } - - int height = array.GetLength(1); - -#if SPAN_RUNTIME_SUPPORT - ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, column); - - return new RefEnumerable(ref r0, height, width); -#else - ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, column); - IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0); - - return new RefEnumerable(array, offset, height, width); -#endif - } - /// /// Creates a new instance of the struct wrapping a layer in a 3D array. /// diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.3D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.3D.cs index 7ab7d67ea88..9cf5ce03c28 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.3D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.3D.cs @@ -2,8 +2,6 @@ // 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.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -59,54 +57,5 @@ public void Test_ArrayExtensions_3D_DangerousGetReferenceAt_Index() Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1)); } - - [TestCategory("ArrayExtensions")] - [TestMethod] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] - [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] - public void Test_ArrayExtensions_3D_GetRow_Rectangle() - { - int[,,] array = new int[10, 20, 12]; - - int j = 0; - foreach (ref int value in array.GetRow(4, 1)) - { - Assert.IsTrue(Unsafe.AreSame(ref value, ref array[4, 1, j++])); - } - - Assert.ThrowsException(() => array.GetRow(-1, 0)); - Assert.ThrowsException(() => array.GetRow(20, -2)); - Assert.ThrowsException(() => array.GetRow(29, 0)); - Assert.ThrowsException(() => array.GetRow(0, 55)); - } - - [TestCategory("ArrayExtensions")] - [TestMethod] - public void Test_ArrayExtensions_3D_GetRow_Empty() - { - int[,,] array = new int[0, 0, 0]; - - Assert.ThrowsException(() => array.GetRow(0, 0)); - } - - [TestCategory("ArrayExtensions")] - [TestMethod] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] - [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] - public void Test_ArrayExtensions_3D_GetColumn_Rectangle() - { - int[,,] array = new int[10, 20, 12]; - - int i = 0; - foreach (ref int value in array.GetColumn(5, 1)) - { - Assert.IsTrue(Unsafe.AreSame(ref value, ref array[5, i++, 1])); - } - - Assert.ThrowsException(() => array.GetColumn(-1, 0)); - Assert.ThrowsException(() => array.GetColumn(0, -4)); - Assert.ThrowsException(() => array.GetColumn(155, 0)); - Assert.ThrowsException(() => array.GetColumn(0, 50)); - } } } From a21c397643649f720191c5400e6d5b7e66b8417e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 17 Oct 2020 02:32:56 +0200 Subject: [PATCH 144/200] Renamed some APIs --- .../Extensions/ArrayExtensions.3D.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index b90e74cffb0..4b821d6d084 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -166,7 +166,7 @@ public static Span AsSpan(this T[,,]? array) /// A instance wrapping the target layer within . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span GetLayerSpan(this T[,,] array, int depth) + public static Span AsSpan(this T[,,] array, int depth) { if (array.IsCovariant()) { @@ -195,7 +195,7 @@ public static Span GetLayerSpan(this T[,,] array, int depth) /// A instance wrapping the target layer within . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Memory GetLayerMemory(this T[,,] array, int depth) + public static Memory AsMemory(this T[,,] array, int depth) { if (array.IsCovariant()) { @@ -228,7 +228,7 @@ public static Memory GetLayerMemory(this T[,,] array, int depth) /// A instance wrapping the target layer within . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span2D GetLayerSpan2D(this T[,,] array, int depth) + public static Span2D AsSpan2D(this T[,,] array, int depth) { return new Span2D(array, depth); } @@ -246,7 +246,7 @@ public static Span2D GetLayerSpan2D(this T[,,] array, int depth) /// A instance wrapping the target layer within . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Memory2D GetLayerMemory2D(this T[,,] array, int depth) + public static Memory2D AsMemory2D(this T[,,] array, int depth) { return new Memory2D(array, depth); } From 05e084feb72e01c7602c6419024b040ecd79325b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 17 Oct 2020 12:43:27 +0200 Subject: [PATCH 145/200] Minor code tweaks --- .../Helpers/ParallelHelper.For.IAction.cs | 1 - .../Helpers/ParallelHelper.For.IAction2D.cs | 1 - .../Helpers/ParallelHelper.ForEach.IInAction.cs | 1 - .../Helpers/ParallelHelper.ForEach.IInAction2D.cs | 5 +++-- .../Helpers/ParallelHelper.ForEach.IRefAction.cs | 1 - .../Helpers/ParallelHelper.ForEach.IRefAction2D.cs | 5 +++-- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.For.IAction.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.For.IAction.cs index 9b267ed5da7..e63deb17727 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.For.IAction.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.For.IAction.cs @@ -218,7 +218,6 @@ public ActionInvoker( /// Processes the batch of actions at a specified index /// /// The index of the batch to process - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Invoke(int i) { int diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.For.IAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.For.IAction2D.cs index ba000e768f8..a6ff50a2893 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.For.IAction2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.For.IAction2D.cs @@ -312,7 +312,6 @@ public Action2DInvoker( /// Processes the batch of actions at a specified index /// /// The index of the batch to process - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Invoke(int i) { int diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction.cs index 79c72ca122b..d8996f77ec6 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction.cs @@ -135,7 +135,6 @@ public InActionInvoker( /// Processes the batch of actions at a specified index /// /// The index of the batch to process - [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void Invoke(int i) { int diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs index 4fad95559ba..4b352f60d00 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs @@ -132,7 +132,6 @@ public InActionInvokerWithReadOnlyMemory2D( /// Processes the batch of actions at a specified index /// /// The index of the batch to process - [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void Invoke(int i) { int @@ -149,7 +148,9 @@ public unsafe void Invoke(int i) for (int x = 0; x < width; x++) { - Unsafe.AsRef(this.action).Invoke(Unsafe.Add(ref r0, (IntPtr)(void*)(uint)x)); + ref TItem ryx = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)x); + + Unsafe.AsRef(this.action).Invoke(ryx); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction.cs index 18802dd0100..cc0a1ee9907 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction.cs @@ -135,7 +135,6 @@ public RefActionInvoker( /// Processes the batch of actions at a specified index /// /// The index of the batch to process - [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void Invoke(int i) { int diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs index 16ac2191fd7..a8feab22c4f 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs @@ -132,7 +132,6 @@ public RefActionInvokerWithReadOnlyMemory2D( /// Processes the batch of actions at a specified index /// /// The index of the batch to process - [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void Invoke(int i) { int @@ -149,7 +148,9 @@ public unsafe void Invoke(int i) for (int x = 0; x < width; x++) { - Unsafe.AsRef(this.action).Invoke(ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)x)); + ref TItem ryx = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)x); + + Unsafe.AsRef(this.action).Invoke(ref ryx); } } } From 6dc0a050e0305bfe4a8f6d78421c9fa9c1eb2f3d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 17 Oct 2020 17:53:00 +0200 Subject: [PATCH 146/200] Added missing internal constructors --- .../Memory/ReadOnlySpan2D{T}.cs | 72 +++++++++++++++++++ .../Memory/Span2D{T}.cs | 72 +++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index af1dd117cac..4c299d70e3a 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -418,6 +418,78 @@ public ReadOnlySpan2D(T[,,] array, int depth, int row, int column, int height, i } #if SPAN_RUNTIME_SUPPORT + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. + /// + /// Thrown when either or are invalid. + /// + /// The total area must match the lenght of . + internal ReadOnlySpan2D(ReadOnlySpan span, int height, int width) + : this(span, 0, height, width, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// 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. + /// + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . + /// + internal ReadOnlySpan2D(ReadOnlySpan span, int offset, int height, int width, int pitch) + { + if ((uint)offset > (uint)span.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + if (width == 0 || height == 0) + { + this = default; + + return; + } + + int + remaining = span.Length - offset, + area = ((width + pitch) * (height - 1)) + width; + + if (area > remaining) + { + ThrowHelper.ThrowArgumentException(); + } + + this.span = MemoryMarshal.CreateSpan(ref span.DangerousGetReferenceAt(offset), height); + this.width = width; + this.pitch = pitch; + } + /// /// Creates a new instance of the struct with the specified parameters. /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 579cb456594..afc7cd8c0c7 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -440,6 +440,78 @@ public Span2D(T[,,] array, int depth, int row, int column, int height, int width } #if SPAN_RUNTIME_SUPPORT + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// The height of the resulting 2D area. + /// The width of each row in the resulting 2D area. + /// + /// Thrown when either or are invalid. + /// + /// The total area must match the lenght of . + internal Span2D(Span span, int height, int width) + : this(span, 0, height, width, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The target to wrap. + /// 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. + /// + /// Thrown when one of the input parameters is out of range. + /// + /// + /// Thrown when the requested area is outside of bounds for . + /// + internal Span2D(Span span, int offset, int height, int width, int pitch) + { + if ((uint)offset > (uint)span.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); + } + + if (height < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); + } + + if (width < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); + } + + if (pitch < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); + } + + if (width == 0 || height == 0) + { + this = default; + + return; + } + + int + remaining = span.Length - offset, + area = ((width + pitch) * (height - 1)) + width; + + if (area > remaining) + { + ThrowHelper.ThrowArgumentException(); + } + + this.span = MemoryMarshal.CreateSpan(ref span.DangerousGetReferenceAt(offset), height); + this.width = width; + this.Pitch = pitch; + } + /// /// Creates a new instance of the struct with the specified parameters. /// From a10034524b116af00cb2397045423fcdf1c8b9eb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 17 Oct 2020 17:55:54 +0200 Subject: [PATCH 147/200] Added [ReadOnly]Span.AsSpan2D extensions --- .../Extensions/MemoryExtensions.cs | 2 + .../Extensions/ReadOnlyMemoryExtensions.cs | 3 ++ .../Extensions/ReadOnlySpanExtensions.cs | 47 +++++++++++++++++++ .../Extensions/SpanExtensions.cs | 47 +++++++++++++++++++ 4 files changed, 99 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs index cb246453660..e2b9d434cad 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs @@ -6,7 +6,9 @@ using System.Diagnostics.Contracts; using System.IO; using System.Runtime.CompilerServices; +#if SPAN_RUNTIME_SUPPORT using Microsoft.Toolkit.HighPerformance.Memory; +#endif using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream; namespace Microsoft.Toolkit.HighPerformance.Extensions diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs index ab6214e75c1..0c1c6eb821f 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs @@ -6,7 +6,9 @@ using System.Diagnostics.Contracts; using System.IO; using System.Runtime.CompilerServices; +#if SPAN_RUNTIME_SUPPORT using Microsoft.Toolkit.HighPerformance.Memory; +#endif using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream; namespace Microsoft.Toolkit.HighPerformance.Extensions @@ -75,6 +77,7 @@ public static ReadOnlyMemory2D AsMemory2D(this ReadOnlyMemory memory, i /// /// Thrown when has an invalid data store. [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Stream AsStream(this ReadOnlyMemory memory) { return MemoryStream.Create(memory, true); diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs index 47428db40a2..e7189666b14 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; +using Microsoft.Toolkit.HighPerformance.Memory; namespace Microsoft.Toolkit.HighPerformance.Extensions { @@ -146,6 +147,52 @@ public static unsafe ref readonly T DangerousGetLookupReferenceAt(this ReadOn return ref r1; } +#if SPAN_RUNTIME_SUPPORT + /// + /// 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 ReadOnlySpan2D(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 ReadOnlySpan2D(span, offset, height, width, pitch); + } +#endif + /// /// Gets the index of an element of a given from its reference. /// diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs index 52dac209876..65d4b0c96d8 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; +using Microsoft.Toolkit.HighPerformance.Memory; namespace Microsoft.Toolkit.HighPerformance.Extensions { @@ -48,6 +49,52 @@ public static unsafe ref T DangerousGetReferenceAt(this Span span, int i) return ref ri; } +#if SPAN_RUNTIME_SUPPORT + /// + /// 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 Span2D AsSpan2D(this Span span, int height, int width) + { + return new Span2D(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 Span2D AsSpan2D(this Span span, int offset, int height, int width, int pitch) + { + return new Span2D(span, offset, height, width, pitch); + } +#endif + /// /// Casts a of one primitive type to of bytes. /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety. From ff6125613c6a84b8d486e39d5dc8e27dd055d8b9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 19 Oct 2020 15:54:35 +0200 Subject: [PATCH 148/200] Added comment for GCHandle.Alloc exception --- .../Buffers/Internals/RawObjectMemoryManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager.cs index 795ea6da4a5..ac919b04f9c 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager.cs @@ -62,10 +62,15 @@ public override unsafe MemoryHandle Pin(int elementIndex = 0) ThrowArgumentOutOfRangeExceptionForInvalidElementIndex(); } + // Allocating a pinned handle for the array with fail and throw an exception + // if the array contains non blittable data. This is the expected behavior and + // the same happens when trying to pin a Memory instance obtained through + // traditional means (eg. via the implicit T[] array conversion), if T is a + // reference type or a type containing some references. + GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned); ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset); ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)elementIndex); void* p = Unsafe.AsPointer(ref r1); - GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned); return new MemoryHandle(p, handle); } From 0e65ef9fbc681fcb4c08e73091c65a78d3943015 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 19 Oct 2020 16:59:54 +0200 Subject: [PATCH 149/200] Fixed incorrect file name --- .../{RawObjectMemoryManager.cs => RawObjectMemoryManager{T}.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Microsoft.Toolkit.HighPerformance/Buffers/Internals/{RawObjectMemoryManager.cs => RawObjectMemoryManager{T}.cs} (100%) diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager{T}.cs similarity index 100% rename from Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager.cs rename to Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager{T}.cs From d505dca32541e6ddfee00890bdb65c44d52ff855 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 21 Oct 2020 14:11:00 +0200 Subject: [PATCH 150/200] Fixed [Memory|Span]2D.IsEmpty property --- .../Memory/Memory2D{T}.cs | 2 +- .../Memory/ReadOnlyMemory2D{T}.cs | 2 +- .../Memory/ReadOnlySpan2D{T}.cs | 2 +- .../Memory/Span2D{T}.cs | 2 +- .../Memory/Test_Memory2D{T}.cs | 14 ++++++++++++++ .../Memory/Test_Span2D{T}.cs | 14 ++++++++++++++ 6 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index b1a15a60cfd..6f582a23686 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -570,7 +570,7 @@ public static Memory2D DangerousCreate(object instance, ref T value, int heig public bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (this.height | this.width) == 0; + get => this.height == 0 || this.width == 0; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 7aa57c20d78..44aa9a94902 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -627,7 +627,7 @@ public static ReadOnlyMemory2D DangerousCreate(object instance, ref T value, public bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (this.height | this.width) == 0; + get => this.height == 0 || this.width == 0; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 4c299d70e3a..e89b2d70c70 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -532,7 +532,7 @@ public static ReadOnlySpan2D DangerousCreate(in T value, int height, int widt public bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (Height | Width) == 0; + get => Height == 0 || this.width == 0; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index afc7cd8c0c7..6d94aea187f 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -554,7 +554,7 @@ public static Span2D DangerousCreate(ref T value, int height, int width, int public bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (Height | this.width) == 0; + get => Height == 0 || this.width == 0; } /// diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs index d1e8e1a0ba5..41d1bfbf423 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs @@ -34,6 +34,20 @@ public void Test_Memory2DT_Empty() Assert.AreEqual(empty2.Size, 0); Assert.AreEqual(empty2.Width, 0); Assert.AreEqual(empty2.Height, 0); + + Memory2D empty3 = new int[4, 0]; + + Assert.IsTrue(empty3.IsEmpty); + Assert.AreEqual(empty3.Size, 0); + Assert.AreEqual(empty3.Width, 0); + Assert.AreEqual(empty3.Height, 4); + + Memory2D empty4 = new int[0, 7]; + + Assert.IsTrue(empty4.IsEmpty); + Assert.AreEqual(empty4.Size, 0); + Assert.AreEqual(empty4.Width, 7); + Assert.AreEqual(empty4.Height, 0); } [TestCategory("Memory2DT")] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs index 2ab310ba19b..2132fa4e134 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -33,6 +33,20 @@ public void Test_Span2DT_Empty() Assert.AreEqual(empty2.Size, 0); Assert.AreEqual(empty2.Width, 0); Assert.AreEqual(empty2.Height, 0); + + Span2D empty3 = new int[4, 0]; + + Assert.IsTrue(empty3.IsEmpty); + Assert.AreEqual(empty3.Size, 0); + Assert.AreEqual(empty3.Width, 0); + Assert.AreEqual(empty3.Height, 4); + + Span2D empty4 = new int[0, 7]; + + Assert.IsTrue(empty4.IsEmpty); + Assert.AreEqual(empty4.Size, 0); + Assert.AreEqual(empty4.Width, 7); + Assert.AreEqual(empty4.Height, 0); } #if !WINDOWS_UWP From b40ac2adc9b0e18f8d124bf332724ce3508e7370 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 21 Oct 2020 22:05:54 +0200 Subject: [PATCH 151/200] Added OverflowHelper, enabled C# 9 --- .../Memory/Internals/OverflowHelper.cs | 45 +++++++++++++++++++ .../Microsoft.Toolkit.HighPerformance.csproj | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs b/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs new file mode 100644 index 00000000000..662052adaad --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs @@ -0,0 +1,45 @@ +using System.Runtime.CompilerServices; +using static System.Math; + +namespace Microsoft.Toolkit.HighPerformance.Memory.Internals +{ + /// + /// A helper to validate arithmetic operations for and . + /// + internal static class OverflowHelper + { + /// + /// Ensures that the input parameters will not exceed the maximum native int value when indexing. + /// + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map (the distance between each row). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void EnsureIsInNativeIntRange(int height, int width, int pitch) + { + // As per the layout used in the Memory2D and Span2D types, we have the + // following memory representation with respect to height, width and pitch: + // + // _________width_________ ________... + // / \/ + // | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |_ + // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | | + // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | | + // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |_height + // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- |_| + // | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | + // | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | + // ...__pitch__/ + // + // The indexing logic works on nint values in unchecked mode, with no overflow checks, + // which means it relies on the maximum element index to always be within <= nint.MaxValue. + // To ensure no overflows will ever occur there, we need to ensure that no instance can be + // created with parameters that could cause an overflow in case any item was accessed, so we + // need to ensure no overflows occurs when calculating the index of the last item in each view. + // The logic below calculates that index with overflow checks, throwing if one is detected. + // Note that we're subtracting 1 to the height as we don't want to include the trailing pitch + // for the 2D memory area, and also 1 to the width as the index is 0-based, as usual. + _ = checked((((nint)width + pitch) * Max(unchecked(height - 1), 0)) + Max(unchecked(width - 1), 0)); + } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj index 7f2d3956c53..df03da93cd0 100644 --- a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj +++ b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj @@ -2,7 +2,7 @@ netstandard1.4;netstandard2.0;netstandard2.1;netcoreapp2.1;netcoreapp3.1 - 8.0 + preview enable true Windows Community Toolkit High Performance .NET Standard From 5c9344b1f996ef31b72cc229466fa2fcc4acf8d0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 21 Oct 2020 23:49:18 +0200 Subject: [PATCH 152/200] Added 32 bit overflow checks in constructors --- .../Memory/Internals/OverflowHelper.cs | 20 ++++++++++++++++++- .../Memory/Memory2D{T}.cs | 12 +++++------ .../Memory/ReadOnlyMemory2D{T}.cs | 12 +++++------ .../Memory/ReadOnlySpan2D{T}.cs | 10 ++++++---- .../Memory/Span2D{T}.cs | 10 ++++++---- 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs b/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs index 662052adaad..8e14d5dc628 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs @@ -1,4 +1,6 @@ -using System.Runtime.CompilerServices; +using System; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; using static System.Math; namespace Microsoft.Toolkit.HighPerformance.Memory.Internals @@ -14,6 +16,7 @@ internal static class OverflowHelper /// The height of the 2D memory area to map. /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map (the distance between each row). + /// Throw when the inputs don't fit in the expected range. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void EnsureIsInNativeIntRange(int height, int width, int pitch) { @@ -41,5 +44,20 @@ public static void EnsureIsInNativeIntRange(int height, int width, int pitch) // for the 2D memory area, and also 1 to the width as the index is 0-based, as usual. _ = checked((((nint)width + pitch) * Max(unchecked(height - 1), 0)) + Max(unchecked(width - 1), 0)); } + + /// + /// Ensures that the input parameters will not exceed the maximum native int value when indexing. + /// + /// The height of the 2D memory area to map. + /// The width of the 2D memory area to map. + /// The pitch of the 2D memory area to map (the distance between each row). + /// The area resulting from the given parameters. + /// Throw when the inputs don't fit in the expected range. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ComputeInt32Area(int height, int width, int pitch) + { + return checked(((width + pitch) * Max(unchecked(height - 1), 0)) + width); + } } } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 6f582a23686..8a6949da8e9 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -118,8 +118,8 @@ public Memory2D(T[] array, int offset, int height, int width, int pitch) } int - remaining = array.Length - offset, - area = ((width + pitch) * (height - 1)) + width; + area = OverflowHelper.ComputeInt32Area(height, width, pitch), + remaining = array.Length - offset; if (area > remaining) { @@ -372,8 +372,8 @@ public Memory2D(MemoryManager memoryManager, int offset, int height, int widt } int - remaining = length - offset, - area = ((width + pitch) * (height - 1)) + width; + area = OverflowHelper.ComputeInt32Area(height, width, pitch), + remaining = length - offset; if (area > remaining) { @@ -451,8 +451,8 @@ internal Memory2D(Memory memory, int offset, int height, int width, int pitch } int - remaining = memory.Length - offset, - area = ((width + pitch) * (height - 1)) + width; + area = OverflowHelper.ComputeInt32Area(height, width, pitch), + remaining = memory.Length - offset; if (area > remaining) { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 44aa9a94902..6111e5a758c 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -182,8 +182,8 @@ public ReadOnlyMemory2D(T[] array, int offset, int height, int width, int pitch) } int - remaining = array.Length - offset, - area = ((width + pitch) * (height - 1)) + width; + area = OverflowHelper.ComputeInt32Area(height, width, pitch), + remaining = array.Length - offset; if (area > remaining) { @@ -436,8 +436,8 @@ public ReadOnlyMemory2D(MemoryManager memoryManager, int offset, int height, } int - remaining = length - offset, - area = ((width + pitch) * (height - 1)) + width; + area = OverflowHelper.ComputeInt32Area(height, width, pitch), + remaining = length - offset; if (area > remaining) { @@ -515,8 +515,8 @@ internal ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int height, int } int - remaining = memory.Length - offset, - area = ((width + pitch) * (height - 1)) + width; + area = OverflowHelper.ComputeInt32Area(height, width, pitch), + remaining = memory.Length - offset; if (area > remaining) { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index e89b2d70c70..5ddf45e0531 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -104,6 +104,8 @@ public unsafe ReadOnlySpan2D(void* pointer, int height, int width, int pitch) ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } + OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch); + #if SPAN_RUNTIME_SUPPORT this.span = new ReadOnlySpan(pointer, height); #else @@ -205,8 +207,8 @@ public ReadOnlySpan2D(T[] array, int offset, int height, int width, int pitch) } int - remaining = array.Length - offset, - area = ((width + pitch) * (height - 1)) + width; + area = OverflowHelper.ComputeInt32Area(height, width, pitch), + remaining = array.Length - offset; if (area > remaining) { @@ -477,8 +479,8 @@ internal ReadOnlySpan2D(ReadOnlySpan span, int offset, int height, int width, } int - remaining = span.Length - offset, - area = ((width + pitch) * (height - 1)) + width; + area = OverflowHelper.ComputeInt32Area(height, width, pitch), + remaining = span.Length - offset; if (area > remaining) { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 6d94aea187f..f5b96ff04ba 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -133,6 +133,8 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } + OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch); + #if SPAN_RUNTIME_SUPPORT this.span = new Span(pointer, height); #else @@ -227,8 +229,8 @@ public Span2D(T[] array, int offset, int height, int width, int pitch) } int - remaining = array.Length - offset, - area = ((width + pitch) * (height - 1)) + width; + area = OverflowHelper.ComputeInt32Area(height, width, pitch), + remaining = array.Length - offset; if (area > remaining) { @@ -499,8 +501,8 @@ internal Span2D(Span span, int offset, int height, int width, int pitch) } int - remaining = span.Length - offset, - area = ((width + pitch) * (height - 1)) + width; + area = OverflowHelper.ComputeInt32Area(height, width, pitch), + remaining = span.Length - offset; if (area > remaining) { From ea621fcf14750a596bd325a22dd0fa190868d959 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 22 Oct 2020 01:19:30 +0200 Subject: [PATCH 153/200] Switched (IntPtr)(void*) casts with (nint) --- .../Internals/RawObjectMemoryManager{T}.cs | 2 +- .../Buffers/StringPool.cs | 36 +++--- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 17 ++- .../Enumerables/ReadOnlySpanEnumerable{T}.cs | 20 ++-- .../Enumerables/RefEnumerable{T}.cs | 25 ++-- .../Enumerables/SpanEnumerable{T}.cs | 30 ++--- .../Extensions/ArrayExtensions.1D.cs | 14 +-- .../Extensions/ArrayExtensions.2D.cs | 10 +- .../Extensions/ArrayExtensions.3D.cs | 6 +- .../Extensions/ReadOnlySpanExtensions.cs | 16 +-- .../Extensions/SpanExtensions.cs | 12 +- .../Extensions/StringExtensions.cs | 10 +- .../Helpers/HashCode{T}.cs | 6 +- .../ParallelHelper.ForEach.IInAction.cs | 4 +- .../ParallelHelper.ForEach.IInAction2D.cs | 4 +- .../ParallelHelper.ForEach.IRefAction.cs | 4 +- .../ParallelHelper.ForEach.IRefAction2D.cs | 4 +- .../Memory/Memory2D{T}.cs | 13 +-- .../Memory/ReadOnlyMemory2D{T}.cs | 13 +-- .../Memory/ReadOnlySpan2D{T}.Enumerator.cs | 12 +- .../Memory/ReadOnlySpan2D{T}.cs | 73 ++++++------ .../Memory/Span2D{T}.Enumerator.cs | 12 +- .../Memory/Span2D{T}.cs | 107 ++++++++---------- 23 files changed, 195 insertions(+), 255 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager{T}.cs index ac919b04f9c..89ce81ea208 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager{T}.cs @@ -69,7 +69,7 @@ public override unsafe MemoryHandle Pin(int elementIndex = 0) // reference type or a type containing some references. GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned); ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset); - ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)elementIndex); + ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)elementIndex); void* p = Unsafe.AsPointer(ref r1); return new MemoryHandle(p, handle); diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/StringPool.cs b/Microsoft.Toolkit.HighPerformance/Buffers/StringPool.cs index d2a98ae8c94..b2f75bbf0d3 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/StringPool.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/StringPool.cs @@ -536,7 +536,7 @@ private unsafe ref string TryGet(ReadOnlySpan span, int hashcode) (uint)i < (uint)length; i = entry.NextIndex) { - entry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)i); + entry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)i); if (entry.HashCode == hashcode && entry.Value!.AsSpan().SequenceEqual(span)) @@ -556,7 +556,7 @@ private unsafe ref string TryGet(ReadOnlySpan span, int hashcode) /// The new instance to store. /// The precomputed hashcode for . [MethodImpl(MethodImplOptions.NoInlining)] - private unsafe void Insert(string value, int hashcode) + private void Insert(string value, int hashcode) { ref int bucketsRef = ref this.buckets.DangerousGetReference(); ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference(); @@ -571,7 +571,7 @@ private unsafe void Insert(string value, int hashcode) entryIndex = heapEntriesRef.MapIndex; heapIndex = 0; - ref MapEntry removedEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex); + ref MapEntry removedEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex); // The removal logic can be extremely optimized in this case, as we // can retrieve the precomputed hashcode for the target entry by doing @@ -588,9 +588,9 @@ private unsafe void Insert(string value, int hashcode) } int bucketIndex = hashcode & (this.buckets.Length - 1); - ref int targetBucket = ref Unsafe.Add(ref bucketsRef, (IntPtr)(void*)(uint)bucketIndex); - ref MapEntry targetMapEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex); - ref HeapEntry targetHeapEntry = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)heapIndex); + ref int targetBucket = ref Unsafe.Add(ref bucketsRef, (nint)(uint)bucketIndex); + ref MapEntry targetMapEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex); + ref HeapEntry targetHeapEntry = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)heapIndex); // Assign the values in the new map entry targetMapEntry.HashCode = hashcode; @@ -616,7 +616,7 @@ private unsafe void Insert(string value, int hashcode) /// The index of the target map node to remove. /// The input instance needs to already exist in the map. [MethodImpl(MethodImplOptions.NoInlining)] - private unsafe void Remove(int hashcode, int mapIndex) + private void Remove(int hashcode, int mapIndex) { ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference(); int @@ -628,7 +628,7 @@ private unsafe void Remove(int hashcode, int mapIndex) // value we're looking for is guaranteed to be present while (true) { - ref MapEntry candidate = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex); + ref MapEntry candidate = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex); // Check the current value for a match if (entryIndex == mapIndex) @@ -636,7 +636,7 @@ private unsafe void Remove(int hashcode, int mapIndex) // If this was not the first list node, update the parent as well if (lastIndex != EndOfList) { - ref MapEntry lastEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)lastIndex); + ref MapEntry lastEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)lastIndex); lastEntry.NextIndex = candidate.NextIndex; } @@ -662,14 +662,14 @@ private unsafe void Remove(int hashcode, int mapIndex) /// /// The index of the target heap node to update. [MethodImpl(MethodImplOptions.NoInlining)] - private unsafe void UpdateTimestamp(ref int heapIndex) + private void UpdateTimestamp(ref int heapIndex) { int currentIndex = heapIndex, count = this.count; ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference(); ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference(); - ref HeapEntry root = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)currentIndex); + ref HeapEntry root = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)currentIndex); uint timestamp = this.timestamp; // Check if incrementing the current timestamp for the heap node to update @@ -721,7 +721,7 @@ private unsafe void UpdateTimestamp(ref int heapIndex) // Check and update the left child, if necessary if (left < count) { - ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)left); + ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)left); if (child.Timestamp < minimum.Timestamp) { @@ -733,7 +733,7 @@ private unsafe void UpdateTimestamp(ref int heapIndex) // Same check as above for the right child if (right < count) { - ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)right); + ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)right); if (child.Timestamp < minimum.Timestamp) { @@ -752,8 +752,8 @@ private unsafe void UpdateTimestamp(ref int heapIndex) } // Update the indices in the respective map entries (accounting for the swap) - Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)root.MapIndex).HeapIndex = targetIndex; - Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)minimum.MapIndex).HeapIndex = currentIndex; + Unsafe.Add(ref mapEntriesRef, (nint)(uint)root.MapIndex).HeapIndex = targetIndex; + Unsafe.Add(ref mapEntriesRef, (nint)(uint)minimum.MapIndex).HeapIndex = currentIndex; currentIndex = targetIndex; @@ -764,7 +764,7 @@ private unsafe void UpdateTimestamp(ref int heapIndex) minimum = temp; // Update the reference to the root node - root = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)currentIndex); + root = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)currentIndex); } Fallback: @@ -787,14 +787,14 @@ private unsafe void UpdateTimestamp(ref int heapIndex) /// a given number of nodes, those are all contiguous from the start of the array. /// [MethodImpl(MethodImplOptions.NoInlining)] - private unsafe void UpdateAllTimestamps() + private void UpdateAllTimestamps() { int count = this.count; ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference(); for (int i = 0; i < count; i++) { - Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)i).Timestamp = (uint)i; + Unsafe.Add(ref heapEntriesRef, (nint)(uint)i).Timestamp = (uint)i; } } } diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index 9c9bd8db320..bc3a16ee932 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -115,13 +115,10 @@ public readonly ref readonly T Current #if SPAN_RUNTIME_SUPPORT return ref this.span.DangerousGetReferenceAt(this.position); #else - unsafe - { - ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.position); + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.position); - return ref ri; - } + return ref ri; #endif } } @@ -133,7 +130,7 @@ public readonly ref readonly T Current /// /// Thrown when is shorter than the source instance. /// - public readonly unsafe void CopyTo(Span destination) + public readonly void CopyTo(Span destination) { #if SPAN_RUNTIME_SUPPORT if (this.step == 1) @@ -158,7 +155,7 @@ public readonly unsafe void CopyTo(Span destination) for (int i = 0, j = 0; i < length; i += this.step, j++) { - Unsafe.Add(ref destinationRef, (IntPtr)(void*)(uint)j) = Unsafe.Add(ref sourceRef, (IntPtr)(void*)(uint)i); + Unsafe.Add(ref destinationRef, (nint)(uint)j) = Unsafe.Add(ref sourceRef, (nint)(uint)i); } } @@ -187,7 +184,7 @@ public readonly bool TryCopyTo(Span destination) /// [Pure] - public readonly unsafe T[] ToArray() + public readonly T[] ToArray() { #if SPAN_RUNTIME_SUPPORT // Fast path for contiguous items @@ -218,7 +215,7 @@ public readonly unsafe T[] ToArray() for (int i = 0, j = 0; i < length; i += this.step, j++) { - Unsafe.Add(ref destinationRef, (IntPtr)(void*)(uint)j) = Unsafe.Add(ref sourceRef, (IntPtr)(void*)(uint)i); + Unsafe.Add(ref destinationRef, (nint)(uint)j) = Unsafe.Add(ref sourceRef, (nint)(uint)i); } return array; diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs index 0da385b4651..6a0724ed01e 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs @@ -66,14 +66,11 @@ public readonly Item Current get { #if SPAN_RUNTIME_SUPPORT - unsafe - { - ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.index); + ref T r0 = ref MemoryMarshal.GetReference(this.span); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index); - // See comment in SpanEnumerable about this - return new Item(ref ri, this.index); - } + // See comment in SpanEnumerable about this + return new Item(ref ri, this.index); #else return new Item(this.span, this.index); #endif @@ -132,13 +129,10 @@ public ref readonly T Value #if SPAN_RUNTIME_SUPPORT return ref MemoryMarshal.GetReference(this.span); #else - unsafe - { - ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.index); + ref T r0 = ref MemoryMarshal.GetReference(this.span); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index); - return ref ri; - } + return ref ri; #endif } } diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 3157bf9dc3d..19589426b3a 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -115,13 +115,10 @@ public readonly ref T Current #if SPAN_RUNTIME_SUPPORT return ref this.span.DangerousGetReferenceAt(this.position); #else - unsafe - { - ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.position); + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.position); - return ref ri; - } + return ref ri; #endif } } @@ -129,7 +126,7 @@ public readonly ref T Current /// /// Clears the contents of the current instance. /// - public readonly unsafe void Clear() + public readonly void Clear() { #if SPAN_RUNTIME_SUPPORT // Fast path for contiguous items @@ -149,7 +146,7 @@ public readonly unsafe void Clear() for (int i = 0; i < length; i += this.step) { - Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i) = default!; + Unsafe.Add(ref r0, (nint)(uint)i) = default!; } } @@ -160,7 +157,7 @@ public readonly unsafe void Clear() /// /// Thrown when is shorter than the source instance. /// - public readonly unsafe void CopyTo(Span destination) + public readonly void CopyTo(Span destination) { #if SPAN_RUNTIME_SUPPORT if (this.step == 1) @@ -185,7 +182,7 @@ public readonly unsafe void CopyTo(Span destination) for (int i = 0, j = 0; i < length; i += this.step, j++) { - Unsafe.Add(ref destinationRef, (IntPtr)(void*)(uint)j) = Unsafe.Add(ref sourceRef, (IntPtr)(void*)(uint)i); + Unsafe.Add(ref destinationRef, (nint)(uint)j) = Unsafe.Add(ref sourceRef, (nint)(uint)i); } } @@ -220,7 +217,7 @@ public readonly bool TryCopyTo(Span destination) /// This method will always return the whole sequence from the start, ignoring the /// current position in case the sequence has already been enumerated in part. /// - public readonly unsafe void Fill(T value) + public readonly void Fill(T value) { #if SPAN_RUNTIME_SUPPORT if (this.step == 1) @@ -239,7 +236,7 @@ public readonly unsafe void Fill(T value) for (int i = 0; i < length; i += this.step) { - Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i) = value; + Unsafe.Add(ref r0, (nint)(uint)i) = value; } } @@ -254,7 +251,7 @@ public readonly unsafe void Fill(T value) /// ignoring the current position in case the sequence has already been enumerated in part. /// [Pure] - public readonly unsafe T[] ToArray() + public readonly T[] ToArray() { #if SPAN_RUNTIME_SUPPORT if (this.step == 1) @@ -284,7 +281,7 @@ public readonly unsafe T[] ToArray() for (int i = 0, j = 0; i < length; i += this.step, j++) { - Unsafe.Add(ref destinationRef, (IntPtr)(void*)(uint)j) = Unsafe.Add(ref sourceRef, (IntPtr)(void*)(uint)i); + Unsafe.Add(ref destinationRef, (nint)(uint)j) = Unsafe.Add(ref sourceRef, (nint)(uint)i); } return array; diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs index ba39222fba9..618f1419ace 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs @@ -66,19 +66,16 @@ public readonly Item Current get { #if SPAN_RUNTIME_SUPPORT - unsafe - { - ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.index); + ref T r0 = ref MemoryMarshal.GetReference(this.span); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index); - // On .NET Standard 2.1 and .NET Core (or on any target that offers runtime - // support for the Span types), we can save 4 bytes by piggybacking the - // current index in the length of the wrapped span. We're going to use the - // first item as the target reference, and the length as a host for the - // current original offset. This is not possible on eg. .NET Standard 2.0, - // as we lack the API to create Span-s from arbitrary references. - return new Item(ref ri, this.index); - } + // On .NET Standard 2.1 and .NET Core (or on any target that offers runtime + // support for the Span types), we can save 4 bytes by piggybacking the + // current index in the length of the wrapped span. We're going to use the + // first item as the target reference, and the length as a host for the + // current original offset. This is not possible on eg. .NET Standard 2.0, + // as we lack the API to create Span-s from arbitrary references. + return new Item(ref ri, this.index); #else return new Item(this.span, this.index); #endif @@ -137,13 +134,10 @@ public ref T Value #if SPAN_RUNTIME_SUPPORT return ref MemoryMarshal.GetReference(this.span); #else - unsafe - { - ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)this.index); + ref T r0 = ref MemoryMarshal.GetReference(this.span); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index); - return ref ri; - } + return ref ri; #endif } } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs index fc54dfb54b6..d8d8b4bae8b 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs @@ -54,18 +54,18 @@ public static ref T DangerousGetReference(this T[] array) /// 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 unsafe ref T DangerousGetReferenceAt(this T[] array, int i) + public static ref T DangerousGetReferenceAt(this T[] array, int i) { #if NETCORE_RUNTIME var arrayData = Unsafe.As(array); ref T r0 = ref Unsafe.As(ref arrayData.Data); - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i); return ref ri; #else IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset(); ref T r0 = ref array.DangerousGetObjectDataReferenceAt(offset); - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i); return ref ri; #endif @@ -102,11 +102,11 @@ private sealed class RawArrayData /// The number of occurrences of in . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe int Count(this T[] array, T value) + public static int Count(this T[] array, T value) where T : IEquatable { ref T r0 = ref array.DangerousGetReference(); - IntPtr length = (IntPtr)(void*)(uint)array.Length; + nint length = (nint)(uint)array.Length; return SpanHelper.Count(ref r0, length, value); } @@ -173,11 +173,11 @@ public static SpanTokenizer Tokenize(this T[] array, T separator) /// The Djb2 hash is fully deterministic and with no random components. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe int GetDjb2HashCode(this T[] array) + public static int GetDjb2HashCode(this T[] array) where T : notnull { ref T r0 = ref array.DangerousGetReference(); - IntPtr length = (IntPtr)(void*)(uint)array.Length; + nint length = (nint)(uint)array.Length; return SpanHelper.GetDjb2HashCode(ref r0, length); } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 5052fcc0bbb..56277a3bb04 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -62,13 +62,13 @@ public static ref T DangerousGetReference(this T[,] array) /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe ref T DangerousGetReferenceAt(this T[,] array, int i, int j) + 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, (IntPtr)(void*)(uint)offset); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)offset); return ref ri; #else @@ -77,7 +77,7 @@ public static unsafe ref T DangerousGetReferenceAt(this T[,] array, int i, in index = (i * width) + j; IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset(); ref T r0 = ref array.DangerousGetObjectDataReferenceAt(offset); - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)index); return ref ri; #endif @@ -396,7 +396,7 @@ public static unsafe int Count(this T[,] array, T value) where T : IEquatable { ref T r0 = ref array.DangerousGetReference(); - IntPtr length = (IntPtr)(void*)(uint)array.Length; + nint length = (nint)(uint)array.Length; return SpanHelper.Count(ref r0, length, value); } @@ -415,7 +415,7 @@ public static unsafe int GetDjb2HashCode(this T[,] array) where T : notnull { ref T r0 = ref array.DangerousGetReference(); - IntPtr length = (IntPtr)(void*)(uint)array.Length; + nint length = (nint)(uint)array.Length; return SpanHelper.GetDjb2HashCode(ref r0, length); } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index 4b821d6d084..fa9738d16d4 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -62,13 +62,13 @@ public static ref T DangerousGetReference(this T[,,] array) /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe ref T DangerousGetReferenceAt(this T[,,] array, int i, int j, int k) + public static ref T DangerousGetReferenceAt(this T[,,] array, int i, int j, int k) { #if NETCORE_RUNTIME var arrayData = Unsafe.As(array); int offset = (i * arrayData.Height * arrayData.Width) + (j * arrayData.Width) + k; ref T r0 = ref Unsafe.As(ref arrayData.Data); - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)offset); return ref ri; #else @@ -78,7 +78,7 @@ public static unsafe ref T DangerousGetReferenceAt(this T[,,] array, int i, i index = (i * height * width) + (j * width) + k; IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset(); ref T r0 = ref array.DangerousGetObjectDataReferenceAt(offset); - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)index); return ref ri; #endif diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs index e7189666b14..28526f15f25 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs @@ -41,7 +41,7 @@ public static ref T DangerousGetReference(this ReadOnlySpan span) /// 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 unsafe ref T DangerousGetReferenceAt(this ReadOnlySpan span, int i) + public static ref T DangerousGetReferenceAt(this ReadOnlySpan span, int i) { // Here we assume the input index will never be negative, so we do an unsafe cast to // force the JIT to skip the sign extension when going from int to native int. @@ -74,7 +74,7 @@ public static unsafe ref T DangerousGetReferenceAt(this ReadOnlySpan span, // 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, (IntPtr)(void*)(uint)i); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i); return ref ri; } @@ -118,7 +118,7 @@ public static unsafe ref T DangerousGetReferenceAt(this ReadOnlySpan span, /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe ref readonly T DangerousGetLookupReferenceAt(this ReadOnlySpan span, int i) + public static 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 @@ -142,7 +142,7 @@ public static unsafe ref readonly T DangerousGetLookupReferenceAt(this ReadOn mask = ~negativeFlag, offset = (uint)i & mask; ref T r0 = ref MemoryMarshal.GetReference(span); - ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)offset); + ref T r1 = ref Unsafe.Add(ref r0, (nint)offset); return ref r1; } @@ -242,11 +242,11 @@ public static unsafe int IndexOf(this ReadOnlySpan span, in T value) /// The number of occurrences of in . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe int Count(this ReadOnlySpan span, T value) + public static int Count(this ReadOnlySpan span, T value) where T : IEquatable { ref T r0 = ref MemoryMarshal.GetReference(span); - IntPtr length = (IntPtr)(void*)(uint)span.Length; + nint length = (nint)(uint)span.Length; return SpanHelper.Count(ref r0, length, value); } @@ -368,11 +368,11 @@ public static ReadOnlySpanTokenizer Tokenize(this ReadOnlySpan span, T /// The Djb2 hash is fully deterministic and with no random components. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe int GetDjb2HashCode(this ReadOnlySpan span) + public static int GetDjb2HashCode(this ReadOnlySpan span) where T : notnull { ref T r0 = ref MemoryMarshal.GetReference(span); - IntPtr length = (IntPtr)(void*)(uint)span.Length; + nint length = (nint)(uint)span.Length; return SpanHelper.GetDjb2HashCode(ref r0, length); } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs index 65d4b0c96d8..cc9586e5a05 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs @@ -41,10 +41,10 @@ public static ref T DangerousGetReference(this Span span) /// 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 unsafe ref T DangerousGetReferenceAt(this Span span, int i) + public static ref T DangerousGetReferenceAt(this Span span, int i) { ref T r0 = ref MemoryMarshal.GetReference(span); - ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i); + ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i); return ref ri; } @@ -187,11 +187,11 @@ public static unsafe int IndexOf(this Span span, ref T value) /// The number of occurrences of in . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe int Count(this Span span, T value) + public static int Count(this Span span, T value) where T : IEquatable { ref T r0 = ref MemoryMarshal.GetReference(span); - IntPtr length = (IntPtr)(void*)(uint)span.Length; + nint length = (nint)(uint)span.Length; return SpanHelper.Count(ref r0, length, value); } @@ -258,11 +258,11 @@ public static SpanTokenizer Tokenize(this Span span, T separator) /// The Djb2 hash is fully deterministic and with no random components. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe int GetDjb2HashCode(this Span span) + public static int GetDjb2HashCode(this Span span) where T : notnull { ref T r0 = ref MemoryMarshal.GetReference(span); - IntPtr length = (IntPtr)(void*)(uint)span.Length; + nint length = (nint)(uint)span.Length; return SpanHelper.GetDjb2HashCode(ref r0, length); } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs index c9cdc114c36..5b9c93353ef 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs @@ -48,7 +48,7 @@ public static ref char DangerousGetReference(this string text) /// 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 unsafe ref char DangerousGetReferenceAt(this string text, int i) + public static ref char DangerousGetReferenceAt(this string text, int i) { #if NETCOREAPP3_1 ref char r0 = ref Unsafe.AsRef(text.GetPinnableReference()); @@ -57,7 +57,7 @@ public static unsafe ref char DangerousGetReferenceAt(this string text, int i) #else ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan()); #endif - ref char ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i); + ref char ri = ref Unsafe.Add(ref r0, (nint)(uint)i); return ref ri; } @@ -91,10 +91,10 @@ private sealed class RawStringData /// The number of occurrences of in . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe int Count(this string text, char c) + public static int Count(this string text, char c) { ref char r0 = ref text.DangerousGetReference(); - IntPtr length = (IntPtr)(void*)(uint)text.Length; + nint length = (nint)(uint)text.Length; return SpanHelper.Count(ref r0, length, c); } @@ -160,7 +160,7 @@ public static ReadOnlySpanTokenizer Tokenize(this string text, char separa public static unsafe int GetDjb2HashCode(this string text) { ref char r0 = ref text.DangerousGetReference(); - IntPtr length = (IntPtr)(void*)(uint)text.Length; + nint length = (nint)(uint)text.Length; return SpanHelper.GetDjb2HashCode(ref r0, length); } diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs b/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs index 1f26854fbb3..c4c651782bb 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs @@ -55,7 +55,7 @@ public static int Combine(ReadOnlySpan span) /// The returned hash code is not processed through APIs. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe int CombineValues(ReadOnlySpan span) + internal static int CombineValues(ReadOnlySpan span) { ref T r0 = ref MemoryMarshal.GetReference(span); @@ -64,7 +64,7 @@ internal static unsafe int CombineValues(ReadOnlySpan span) // compiler, so this branch will never actually be executed by the code. if (RuntimeHelpers.IsReferenceOrContainsReferences()) { - return SpanHelper.GetDjb2HashCode(ref r0, (IntPtr)(void*)(uint)span.Length); + return SpanHelper.GetDjb2HashCode(ref r0, (nint)(uint)span.Length); } // Get the info for the target memory area to process. @@ -75,7 +75,7 @@ internal static unsafe int CombineValues(ReadOnlySpan span) // process. In that case it will just compute the byte size as a 32 bit // multiplication with overflow, which is guaranteed never to happen anyway. ref byte rb = ref Unsafe.As(ref r0); - IntPtr length = (IntPtr)(void*)((uint)span.Length * (uint)Unsafe.SizeOf()); + nint length = (nint)((uint)span.Length * (uint)Unsafe.SizeOf()); return SpanHelper.GetDjb2LikeByteHash(ref rb, length); } diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction.cs index d8996f77ec6..b2bcd086d45 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction.cs @@ -135,7 +135,7 @@ public InActionInvoker( /// Processes the batch of actions at a specified index /// /// The index of the batch to process - public unsafe void Invoke(int i) + public void Invoke(int i) { int low = i * this.batchSize, @@ -146,7 +146,7 @@ public unsafe void Invoke(int i) for (int j = low; j < end; j++) { - ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j); + ref TItem rj = ref Unsafe.Add(ref r0, (nint)(uint)j); Unsafe.AsRef(this.action).Invoke(rj); } diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs index 4b352f60d00..096b81d4d13 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs @@ -132,7 +132,7 @@ public InActionInvokerWithReadOnlyMemory2D( /// Processes the batch of actions at a specified index /// /// The index of the batch to process - public unsafe void Invoke(int i) + public void Invoke(int i) { int lowY = i * this.batchHeight, @@ -148,7 +148,7 @@ public unsafe void Invoke(int i) for (int x = 0; x < width; x++) { - ref TItem ryx = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)x); + ref TItem ryx = ref Unsafe.Add(ref r0, (nint)(uint)x); Unsafe.AsRef(this.action).Invoke(ryx); } diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction.cs index cc0a1ee9907..c3521952f34 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction.cs @@ -135,7 +135,7 @@ public RefActionInvoker( /// Processes the batch of actions at a specified index /// /// The index of the batch to process - public unsafe void Invoke(int i) + public void Invoke(int i) { int low = i * this.batchSize, @@ -146,7 +146,7 @@ public unsafe void Invoke(int i) for (int j = low; j < end; j++) { - ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j); + ref TItem rj = ref Unsafe.Add(ref r0, (nint)(uint)j); Unsafe.AsRef(this.action).Invoke(ref rj); } diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs index a8feab22c4f..047aa58752d 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs @@ -132,7 +132,7 @@ public RefActionInvokerWithReadOnlyMemory2D( /// Processes the batch of actions at a specified index /// /// The index of the batch to process - public unsafe void Invoke(int i) + public void Invoke(int i) { int lowY = i * this.batchHeight, @@ -148,7 +148,7 @@ public unsafe void Invoke(int i) for (int x = 0; x < width; x++) { - ref TItem ryx = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)x); + ref TItem ryx = ref Unsafe.Add(ref r0, (nint)(uint)x); Unsafe.AsRef(this.action).Invoke(ref ryx); } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 8a6949da8e9..fc37ccfaad9 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -381,12 +381,7 @@ public Memory2D(MemoryManager memoryManager, int offset, int height, int widt } this.instance = memoryManager; - - unsafe - { - this.offset = (IntPtr)(void*)(uint)offset; - } - + this.offset = (nint)(uint)offset; this.height = height; this.width = width; this.pitch = pitch; @@ -485,11 +480,7 @@ internal Memory2D(Memory memory, int offset, int height, int width, int pitch else if (MemoryMarshal.TryGetMemoryManager>(memory, out var memoryManager, out int memoryManagerStart, out _)) { this.instance = memoryManager; - - unsafe - { - this.offset = (IntPtr)(void*)(uint)(memoryManagerStart + offset); - } + this.offset = (nint)(uint)(memoryManagerStart + offset); } else { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 6111e5a758c..b5cee13b2e4 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -445,12 +445,7 @@ public ReadOnlyMemory2D(MemoryManager memoryManager, int offset, int height, } this.instance = memoryManager; - - unsafe - { - this.offset = (IntPtr)(void*)(uint)offset; - } - + this.offset = (nint)(uint)offset; this.height = height; this.width = width; this.pitch = pitch; @@ -542,11 +537,7 @@ internal ReadOnlyMemory2D(ReadOnlyMemory memory, int offset, int height, int else if (MemoryMarshal.TryGetMemoryManager>(memory, out var memoryManager, out int memoryManagerStart, out _)) { this.instance = memoryManager; - - unsafe - { - this.offset = (IntPtr)(void*)(uint)(memoryManagerStart + offset); - } + this.offset = (nint)(uint)(memoryManagerStart + offset); } else { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs index 5516c4bb574..66b7afc9ec5 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs @@ -26,7 +26,7 @@ public readonly ref partial struct ReadOnlySpan2D /// The returned value shouldn't be used directly: use this extension in a loop. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe ReadOnlyRefEnumerable GetRow(int row) + public ReadOnlyRefEnumerable GetRow(int row) { if ((uint)row >= Height) { @@ -35,7 +35,7 @@ public unsafe ReadOnlyRefEnumerable GetRow(int row) int startIndex = (this.width + this.pitch) * row; ref T r0 = ref DangerousGetReference(); - ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)startIndex); + ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)startIndex); #if SPAN_RUNTIME_SUPPORT return new ReadOnlyRefEnumerable(r1, Width, 1); @@ -54,7 +54,7 @@ public unsafe ReadOnlyRefEnumerable GetRow(int row) /// The returned value shouldn't be used directly: use this extension in a loop. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe ReadOnlyRefEnumerable GetColumn(int column) + public ReadOnlyRefEnumerable GetColumn(int column) { if ((uint)column >= Width) { @@ -62,7 +62,7 @@ public unsafe ReadOnlyRefEnumerable GetColumn(int column) } ref T r0 = ref DangerousGetReference(); - ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)column); + ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column); #if SPAN_RUNTIME_SUPPORT return new ReadOnlyRefEnumerable(r1, Height, this.width + this.pitch); @@ -181,7 +181,7 @@ public bool MoveNext() /// /// Gets the duck-typed property. /// - public readonly unsafe ref readonly T Current + public readonly ref readonly T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -193,7 +193,7 @@ public readonly unsafe ref readonly T Current #endif int index = (this.y * (this.width + this.pitch)) + this.x; - return ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); + return ref Unsafe.Add(ref r0, (nint)(uint)index); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 5ddf45e0531..b0e2eef6483 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -665,22 +665,19 @@ public void CopyTo(Span destination) GetRowSpan(i).CopyTo(destination.Slice(j)); } #else - unsafe - { - int height = Height; - IntPtr width = (IntPtr)(void*)(uint)this.width; + int height = Height; + nint width = (nint)(uint)this.width; - ref T destinationRef = ref MemoryMarshal.GetReference(destination); - IntPtr offset = default; + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + nint offset = 0; - for (int i = 0; i < height; i++) - { - ref T sourceRef = ref DangerousGetReferenceAt(i, 0); + for (int i = 0; i < height; i++) + { + ref T sourceRef = ref DangerousGetReferenceAt(i, 0); - for (IntPtr j = default; (void*)j < (void*)width; j += 1, offset += 1) - { - Unsafe.Add(ref destinationRef, offset) = Unsafe.Add(ref sourceRef, j); - } + for (nint j = 0; j < width; j += 1, offset += 1) + { + Unsafe.Add(ref destinationRef, offset) = Unsafe.Add(ref sourceRef, j); } } #endif @@ -721,20 +718,17 @@ public void CopyTo(Span2D destination) GetRowSpan(i).CopyTo(destination.GetRowSpan(i)); } #else - unsafe + int height = Height; + nint width = (nint)(uint)this.width; + + for (int i = 0; i < height; i++) { - int height = Height; - IntPtr width = (IntPtr)(void*)(uint)this.width; + ref T sourceRef = ref DangerousGetReferenceAt(i, 0); + ref T destinationRef = ref destination.DangerousGetReferenceAt(i, 0); - for (int i = 0; i < height; i++) + for (nint j = 0; j < width; j += 1) { - ref T sourceRef = ref DangerousGetReferenceAt(i, 0); - ref T destinationRef = ref destination.DangerousGetReferenceAt(i, 0); - - for (IntPtr j = default; (void*)j < (void*)width; j += 1) - { - Unsafe.Add(ref destinationRef, j) = Unsafe.Add(ref sourceRef, j); - } + Unsafe.Add(ref destinationRef, j) = Unsafe.Add(ref sourceRef, j); } } #endif @@ -824,7 +818,7 @@ public ref T DangerousGetReference() /// A reference to the element at the specified indices. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe ref T DangerousGetReferenceAt(int i, int j) + public ref T DangerousGetReferenceAt(int i, int j) { #if SPAN_RUNTIME_SUPPORT ref T r0 = ref MemoryMarshal.GetReference(this.span); @@ -833,7 +827,7 @@ public unsafe ref T DangerousGetReferenceAt(int i, int j) #endif int index = (i * (this.width + this.pitch)) + j; - return ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); + return ref Unsafe.Add(ref r0, (nint)(uint)index); } /// @@ -894,7 +888,7 @@ public ReadOnlySpan2D Slice(int row, int column, int width, int height) /// Throw when is out of range. /// The resulting row . [Pure] - public unsafe ReadOnlySpan GetRowSpan(int row) + public ReadOnlySpan GetRowSpan(int row) { if ((uint)row >= (uint)Height) { @@ -903,7 +897,7 @@ public unsafe ReadOnlySpan GetRowSpan(int row) int offset = (this.width + this.pitch) * row; ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); + ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)offset); return MemoryMarshal.CreateReadOnlySpan(ref r1, this.width); } @@ -973,22 +967,19 @@ public bool TryGetSpan(out ReadOnlySpan span) // Skip the initialization if the array is empty if (Size > 0) { - unsafe - { - int height = Height; - IntPtr width = (IntPtr)(void*)(uint)this.width; + int height = Height; + nint width = (nint)(uint)this.width; - ref T destinationRef = ref array.DangerousGetReference(); - IntPtr offset = default; + ref T destinationRef = ref array.DangerousGetReference(); + nint offset = 0; - for (int i = 0; i < height; i++) - { - ref T sourceRef = ref DangerousGetReferenceAt(i, 0); + for (int i = 0; i < height; i++) + { + ref T sourceRef = ref DangerousGetReferenceAt(i, 0); - for (IntPtr j = default; (void*)j < (void*)width; j += 1, offset += 1) - { - Unsafe.Add(ref destinationRef, offset) = Unsafe.Add(ref sourceRef, j); - } + for (nint j = 0; j < width; j += 1, offset += 1) + { + Unsafe.Add(ref destinationRef, offset) = Unsafe.Add(ref sourceRef, j); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index c6d07ed2136..78293848789 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -26,7 +26,7 @@ public readonly ref partial struct Span2D /// The returned value shouldn't be used directly: use this extension in a loop. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe RefEnumerable GetRow(int row) + public RefEnumerable GetRow(int row) { if ((uint)row >= Height) { @@ -35,7 +35,7 @@ public unsafe RefEnumerable GetRow(int row) int startIndex = (this.width + this.Pitch) * row; ref T r0 = ref DangerousGetReference(); - ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)startIndex); + ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)startIndex); #if SPAN_RUNTIME_SUPPORT return new RefEnumerable(ref r1, Width, 1); @@ -54,7 +54,7 @@ public unsafe RefEnumerable GetRow(int row) /// The returned value shouldn't be used directly: use this extension in a loop. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe RefEnumerable GetColumn(int column) + public RefEnumerable GetColumn(int column) { if ((uint)column >= Width) { @@ -62,7 +62,7 @@ public unsafe RefEnumerable GetColumn(int column) } ref T r0 = ref DangerousGetReference(); - ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)column); + ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column); #if SPAN_RUNTIME_SUPPORT return new RefEnumerable(ref r1, Height, this.width + this.Pitch); @@ -181,7 +181,7 @@ public bool MoveNext() /// /// Gets the duck-typed property. /// - public readonly unsafe ref T Current + public readonly ref T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -193,7 +193,7 @@ public readonly unsafe ref T Current #endif int index = (this.y * (this.width + this.pitch)) + this.x; - return ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); + return ref Unsafe.Add(ref r0, (nint)(uint)index); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index f5b96ff04ba..69698c1c183 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -678,19 +678,16 @@ public void Clear() GetRowSpan(i).Clear(); } #else - unsafe + int height = Height; + nint width = (nint)(uint)this.width; + + for (int i = 0; i < height; i++) { - int height = Height; - IntPtr width = (IntPtr)(void*)(uint)this.width; + ref T r0 = ref DangerousGetReferenceAt(i, 0); - for (int i = 0; i < height; i++) + for (nint j = 0; j < width; j += 1) { - ref T r0 = ref DangerousGetReferenceAt(i, 0); - - for (IntPtr j = default; (void*)j < (void*)width; j += 1) - { - Unsafe.Add(ref r0, j) = default!; - } + Unsafe.Add(ref r0, j) = default!; } } #endif @@ -729,22 +726,19 @@ public void CopyTo(Span destination) GetRowSpan(i).CopyTo(destination.Slice(j)); } #else - unsafe - { - int height = Height; - IntPtr width = (IntPtr)(void*)(uint)this.width; + int height = Height; + nint width = (nint)(uint)this.width; - ref T destinationRef = ref MemoryMarshal.GetReference(destination); - IntPtr offset = default; + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + nint offset = 0; - for (int i = 0; i < height; i++) - { - ref T sourceRef = ref DangerousGetReferenceAt(i, 0); + for (int i = 0; i < height; i++) + { + ref T sourceRef = ref DangerousGetReferenceAt(i, 0); - for (IntPtr j = default; (void*)j < (void*)width; j += 1, offset += 1) - { - Unsafe.Add(ref destinationRef, offset) = Unsafe.Add(ref sourceRef, j); - } + for (nint j = 0; j < width; j += 1, offset += 1) + { + Unsafe.Add(ref destinationRef, offset) = Unsafe.Add(ref sourceRef, j); } } #endif @@ -785,20 +779,17 @@ public void CopyTo(Span2D destination) GetRowSpan(i).CopyTo(destination.GetRowSpan(i)); } #else - unsafe + int height = Height; + nint width = (nint)(uint)this.width; + + for (int i = 0; i < height; i++) { - int height = Height; - IntPtr width = (IntPtr)(void*)(uint)this.width; + ref T sourceRef = ref DangerousGetReferenceAt(i, 0); + ref T destinationRef = ref destination.DangerousGetReferenceAt(i, 0); - for (int i = 0; i < height; i++) + for (nint j = 0; j < width; j += 1) { - ref T sourceRef = ref DangerousGetReferenceAt(i, 0); - ref T destinationRef = ref destination.DangerousGetReferenceAt(i, 0); - - for (IntPtr j = default; (void*)j < (void*)width; j += 1) - { - Unsafe.Add(ref destinationRef, j) = Unsafe.Add(ref sourceRef, j); - } + Unsafe.Add(ref destinationRef, j) = Unsafe.Add(ref sourceRef, j); } } #endif @@ -864,19 +855,16 @@ public void Fill(T value) GetRowSpan(i).Fill(value); } #else - unsafe + int height = Height; + nint width = (nint)(uint)this.width; + + for (int i = 0; i < height; i++) { - int height = Height; - IntPtr width = (IntPtr)(void*)(uint)this.width; + ref T r0 = ref DangerousGetReferenceAt(i, 0); - for (int i = 0; i < height; i++) + for (nint j = 0; j < width; j += 1) { - ref T r0 = ref DangerousGetReferenceAt(i, 0); - - for (IntPtr j = default; (void*)j < (void*)width; j += 1) - { - Unsafe.Add(ref r0, j) = value; - } + Unsafe.Add(ref r0, j) = value; } } #endif @@ -931,7 +919,7 @@ public ref T DangerousGetReference() /// A reference to the element at the specified indices. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe ref T DangerousGetReferenceAt(int i, int j) + public ref T DangerousGetReferenceAt(int i, int j) { #if SPAN_RUNTIME_SUPPORT ref T r0 = ref MemoryMarshal.GetReference(this.span); @@ -940,7 +928,7 @@ public unsafe ref T DangerousGetReferenceAt(int i, int j) #endif int index = (i * (this.width + this.Pitch)) + j; - return ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)index); + return ref Unsafe.Add(ref r0, (nint)(uint)index); } /// @@ -1001,7 +989,7 @@ public Span2D Slice(int row, int column, int height, int width) /// Throw when is out of range. /// The resulting row . [Pure] - public unsafe Span GetRowSpan(int row) + public Span GetRowSpan(int row) { if ((uint)row >= (uint)Height) { @@ -1010,7 +998,7 @@ public unsafe Span GetRowSpan(int row) int offset = (this.width + this.Pitch) * row; ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); + ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)offset); return MemoryMarshal.CreateSpan(ref r1, this.width); } @@ -1080,22 +1068,19 @@ public bool TryGetSpan(out Span span) // Skip the initialization if the array is empty if (Size > 0) { - unsafe - { - int height = Height; - IntPtr width = (IntPtr)(void*)(uint)this.width; + int height = Height; + nint width = (nint)(uint)this.width; - ref T destinationRef = ref array.DangerousGetReference(); - IntPtr offset = default; + ref T destinationRef = ref array.DangerousGetReference(); + nint offset = 0; - for (int i = 0; i < height; i++) - { - ref T sourceRef = ref DangerousGetReferenceAt(i, 0); + for (int i = 0; i < height; i++) + { + ref T sourceRef = ref DangerousGetReferenceAt(i, 0); - for (IntPtr j = default; (void*)j < (void*)width; j += 1, offset += 1) - { - Unsafe.Add(ref destinationRef, offset) = Unsafe.Add(ref sourceRef, j); - } + for (nint j = 0; j < width; j += 1, offset += 1) + { + Unsafe.Add(ref destinationRef, offset) = Unsafe.Add(ref sourceRef, j); } } } From a4913691b33d2f722d388cae4468ddbdd9972d0a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 22 Oct 2020 01:25:30 +0200 Subject: [PATCH 154/200] Added Span.DangerousGetReferenceAt nint overload --- .../Extensions/ReadOnlySpanExtensions.cs | 32 +++++++++++++++---- .../Extensions/SpanExtensions.cs | 18 +++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs index 28526f15f25..f674195efa5 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs @@ -43,8 +43,8 @@ public static ref T DangerousGetReference(this ReadOnlySpan span) [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 an unsafe cast to - // force the JIT to skip the sign extension when going from int to native int. + // 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] @@ -55,7 +55,7 @@ public static ref T DangerousGetReferenceAt(this ReadOnlySpan span, int i) // 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 (IntPtr)(void*)(uint) cast, we get the following codegen instead: + // With the (nint)(uint) cast, we get the following codegen instead: // ============================= // L0000: mov rax, [rcx] // L0003: mov edx, edx @@ -68,10 +68,10 @@ public static ref T DangerousGetReferenceAt(this ReadOnlySpan span, int i) // 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 void*, which lets the following IntPtr cast avoid the range check on 32 bit - // (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 + // 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); @@ -79,6 +79,24 @@ public static ref T DangerousGetReferenceAt(this ReadOnlySpan span, int 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. diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs index cc9586e5a05..90895b5dde8 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs @@ -49,6 +49,24 @@ public static ref T DangerousGetReferenceAt(this Span span, int 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 Span span, nint i) + { + ref T r0 = ref MemoryMarshal.GetReference(span); + ref T ri = ref Unsafe.Add(ref r0, i); + + return ref ri; + } + #if SPAN_RUNTIME_SUPPORT /// /// Returns a instance wrapping the underlying data for the given instance. From 41d0a4c1bbccb44141c985d63f59592a952933b3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 22 Oct 2020 01:53:50 +0200 Subject: [PATCH 155/200] Fixed indexing computation in Span2D types --- .../Memory/Internals/OverflowHelper.cs | 5 ++++- .../Memory/ReadOnlySpan2D{T}.Enumerator.cs | 8 ++++---- .../Memory/ReadOnlySpan2D{T}.cs | 15 +++++++-------- .../Memory/Span2D{T}.Enumerator.cs | 8 ++++---- .../Memory/Span2D{T}.cs | 15 +++++++-------- 5 files changed, 26 insertions(+), 25 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs b/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs index 8e14d5dc628..6d2a088055c 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs @@ -42,7 +42,10 @@ public static void EnsureIsInNativeIntRange(int height, int width, int pitch) // The logic below calculates that index with overflow checks, throwing if one is detected. // Note that we're subtracting 1 to the height as we don't want to include the trailing pitch // for the 2D memory area, and also 1 to the width as the index is 0-based, as usual. - _ = checked((((nint)width + pitch) * Max(unchecked(height - 1), 0)) + Max(unchecked(width - 1), 0)); + // Additionally, we're also ensuring that the stride is never greater than int.MaxValue, for + // consistency with how ND arrays work (int.MaxValue as upper bound for each axis), and to + // allow for faster iteration in the RefEnumerable type, when traversing columns. + _ = checked(((nint)(width + pitch) * Max(unchecked(height - 1), 0)) + Max(unchecked(width - 1), 0)); } /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs index 66b7afc9ec5..3a2a9ef783a 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs @@ -33,9 +33,9 @@ public ReadOnlyRefEnumerable GetRow(int row) ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); } - int startIndex = (this.width + this.pitch) * row; + nint startIndex = (nint)(uint)(this.width + this.pitch) * (nint)(uint)row; ref T r0 = ref DangerousGetReference(); - ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)startIndex); + ref T r1 = ref Unsafe.Add(ref r0, startIndex); #if SPAN_RUNTIME_SUPPORT return new ReadOnlyRefEnumerable(r1, Width, 1); @@ -191,9 +191,9 @@ public readonly ref readonly T Current #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif - int index = (this.y * (this.width + this.pitch)) + this.x; + nint index = ((nint)(uint)this.y * (nint)(uint)(this.width + this.pitch)) + (nint)(uint)this.x; - return ref Unsafe.Add(ref r0, (nint)(uint)index); + return ref Unsafe.Add(ref r0, index); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index b0e2eef6483..9c6e601d6dc 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -825,9 +825,9 @@ public ref T DangerousGetReferenceAt(int i, int j) #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif - int index = (i * (this.width + this.pitch)) + j; + nint index = ((nint)(uint)i * (nint)(uint)(this.width + this.pitch)) + (nint)(uint)j; - return ref Unsafe.Add(ref r0, (nint)(uint)index); + return ref Unsafe.Add(ref r0, index); } /// @@ -865,16 +865,15 @@ public ReadOnlySpan2D Slice(int row, int column, int width, int height) ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - int - shift = ((this.width + this.pitch) * row) + column, - pitch = this.pitch + (this.width - width); + nint shift = ((nint)(uint)(this.width + this.pitch) * (nint)(uint)row) + (nint)(uint)column; + int pitch = this.pitch + (this.width - width); #if SPAN_RUNTIME_SUPPORT ref T r0 = ref this.span.DangerousGetReferenceAt(shift); return new ReadOnlySpan2D(r0, height, width, pitch); #else - IntPtr offset = this.offset + (shift * Unsafe.SizeOf()); + IntPtr offset = this.offset + (shift * (nint)(uint)Unsafe.SizeOf()); return new ReadOnlySpan2D(this.instance, offset, height, width, pitch); #endif @@ -895,9 +894,9 @@ public ReadOnlySpan GetRowSpan(int row) ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); } - int offset = (this.width + this.pitch) * row; + nint offset = (nint)(uint)(this.width + this.pitch) * (nint)(uint)row; ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)offset); + ref T r1 = ref Unsafe.Add(ref r0, offset); return MemoryMarshal.CreateReadOnlySpan(ref r1, this.width); } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index 78293848789..34f7d93c51b 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -33,9 +33,9 @@ public RefEnumerable GetRow(int row) ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); } - int startIndex = (this.width + this.Pitch) * row; + nint startIndex = (nint)(uint)(this.width + this.Pitch) * (nint)(uint)row; ref T r0 = ref DangerousGetReference(); - ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)startIndex); + ref T r1 = ref Unsafe.Add(ref r0, startIndex); #if SPAN_RUNTIME_SUPPORT return new RefEnumerable(ref r1, Width, 1); @@ -191,9 +191,9 @@ public readonly ref T Current #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif - int index = (this.y * (this.width + this.pitch)) + this.x; + nint index = ((nint)(uint)this.y * (nint)(uint)(this.width + this.pitch)) + (nint)(uint)this.x; - return ref Unsafe.Add(ref r0, (nint)(uint)index); + return ref Unsafe.Add(ref r0, index); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 69698c1c183..b1286c60a0f 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -926,9 +926,9 @@ public ref T DangerousGetReferenceAt(int i, int j) #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); #endif - int index = (i * (this.width + this.Pitch)) + j; + nint index = ((nint)(uint)i * (nint)(uint)(this.width + this.Pitch)) + (nint)(uint)j; - return ref Unsafe.Add(ref r0, (nint)(uint)index); + return ref Unsafe.Add(ref r0, index); } /// @@ -966,16 +966,15 @@ public Span2D Slice(int row, int column, int height, int width) ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } - int - shift = ((this.width + this.Pitch) * row) + column, - pitch = this.Pitch + (this.width - width); + nint shift = ((nint)(uint)(this.width + this.Pitch) * (nint)(uint)row) + (nint)(uint)column; + int pitch = this.Pitch + (this.width - width); #if SPAN_RUNTIME_SUPPORT ref T r0 = ref this.span.DangerousGetReferenceAt(shift); return new Span2D(ref r0, height, width, pitch); #else - IntPtr offset = this.Offset + (shift * Unsafe.SizeOf()); + IntPtr offset = this.Offset + (shift * (nint)(uint)Unsafe.SizeOf()); return new Span2D(this.Instance, offset, height, width, pitch); #endif @@ -996,9 +995,9 @@ public Span GetRowSpan(int row) ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); } - int offset = (this.width + this.Pitch) * row; + nint offset = (nint)(uint)(this.width + this.Pitch) * (nint)(uint)row; ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)offset); + ref T r1 = ref Unsafe.Add(ref r0, offset); return MemoryMarshal.CreateSpan(ref r1, this.width); } From 3636b3ec5df66f5a251dc15bb98946ffe72c41aa Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 22 Oct 2020 13:13:40 +0200 Subject: [PATCH 156/200] Fixed missing overflow check in constructor --- .../Memory/ReadOnlyMemory2D{T}.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index b5cee13b2e4..c3c933ad2c1 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -104,8 +104,8 @@ public ReadOnlyMemory2D(string text, int offset, int height, int width, int pitc } int - remaining = text.Length - offset, - area = ((width + pitch) * (height - 1)) + width; + area = OverflowHelper.ComputeInt32Area(height, width, pitch), + remaining = text.Length - offset; if (area > remaining) { From 1b624e5797e0eb015a4851931e84264c00d4b88d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 22 Oct 2020 15:11:05 +0200 Subject: [PATCH 157/200] Fixed overflows in [ReadOnly]RefEnumerable --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 48 +++++-------- .../Enumerables/RefEnumerable{T}.cs | 69 ++++++++++--------- 2 files changed, 54 insertions(+), 63 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index bc3a16ee932..e02b4007ba1 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -67,9 +67,9 @@ public ref struct ReadOnlyRefEnumerable [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ReadOnlyRefEnumerable(in T reference, int length, int step) { - this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length * step); + this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length); this.step = step; - this.position = -step; + this.position = -1; } #else /// @@ -84,9 +84,9 @@ internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int { this.instance = instance; this.offset = offset; - this.length = length * step; + this.length = length; this.step = step; - this.position = -step; + this.position = -1; } #endif @@ -100,9 +100,9 @@ internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int public bool MoveNext() { #if SPAN_RUNTIME_SUPPORT - return (this.position += this.step) < this.span.Length; + return ++this.position < this.span.Length; #else - return (this.position += this.step) < this.length; + return ++this.position < this.length; #endif } @@ -113,13 +113,14 @@ public readonly ref readonly T Current get { #if SPAN_RUNTIME_SUPPORT - return ref this.span.DangerousGetReferenceAt(this.position); + ref T r0 = ref this.span.DangerousGetReference(); #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); - ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.position); +#endif + nint offset = (nint)(uint)this.position * (nint)(uint)this.step; + ref T ri = ref Unsafe.Add(ref r0, offset); return ref ri; -#endif } } @@ -146,16 +147,19 @@ public readonly void CopyTo(Span destination) ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); int length = this.length; #endif - if ((uint)destination.Length < (uint)(length / this.step)) + if ((uint)destination.Length < (uint)length) { ThrowArgumentExceptionForDestinationTooShort(); } ref T destinationRef = ref destination.DangerousGetReference(); + nint + step = (nint)(uint)this.step, + offset = 0; - for (int i = 0, j = 0; i < length; i += this.step, j++) + for (int i = 0; i < length; i++, offset += step) { - Unsafe.Add(ref destinationRef, (nint)(uint)j) = Unsafe.Add(ref sourceRef, (nint)(uint)i); + Unsafe.Add(ref destinationRef, i) = Unsafe.Add(ref sourceRef, offset); } } @@ -187,12 +191,6 @@ public readonly bool TryCopyTo(Span destination) public readonly T[] ToArray() { #if SPAN_RUNTIME_SUPPORT - // Fast path for contiguous items - if (this.step == 1) - { - return this.span.ToArray(); - } - int length = this.span.Length; #else int length = this.length; @@ -204,19 +202,9 @@ public readonly T[] ToArray() return Array.Empty(); } -#if SPAN_RUNTIME_SUPPORT - ref T sourceRef = ref this.span.DangerousGetReference(); -#else - ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); -#endif - - T[] array = new T[length / this.step]; - ref T destinationRef = ref array.DangerousGetReference(); + T[] array = new T[length]; - for (int i = 0, j = 0; i < length; i += this.step, j++) - { - Unsafe.Add(ref destinationRef, (nint)(uint)j) = Unsafe.Add(ref sourceRef, (nint)(uint)i); - } + CopyTo(array); return array; } diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 19589426b3a..327b668f8ea 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -67,9 +67,9 @@ public ref struct RefEnumerable [MethodImpl(MethodImplOptions.AggressiveInlining)] internal RefEnumerable(ref T reference, int length, int step) { - this.span = MemoryMarshal.CreateSpan(ref reference, length * step); + this.span = MemoryMarshal.CreateSpan(ref reference, length); this.step = step; - this.position = -step; + this.position = -1; } #else /// @@ -84,9 +84,9 @@ internal RefEnumerable(object? instance, IntPtr offset, int length, int step) { this.instance = instance; this.offset = offset; - this.length = length * step; + this.length = length; this.step = step; - this.position = -step; + this.position = -1; } #endif @@ -100,9 +100,9 @@ internal RefEnumerable(object? instance, IntPtr offset, int length, int step) public bool MoveNext() { #if SPAN_RUNTIME_SUPPORT - return (this.position += this.step) < this.span.Length; + return ++this.position < this.span.Length; #else - return (this.position += this.step) < this.length; + return ++this.position < this.length; #endif } @@ -113,13 +113,21 @@ public readonly ref T Current get { #if SPAN_RUNTIME_SUPPORT - return ref this.span.DangerousGetReferenceAt(this.position); + ref T r0 = ref this.span.DangerousGetReference(); #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); - ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.position); +#endif + + // Here we just offset by shifting down as if we were traversing a 2D array with a + // a single column, with the width of each row represented by the step, the height + // represented by the current position, and with only the first element of each row + // being inspected. We can perform all the indexing operations in this type as nint, + // as the maximum offset is guaranteed never to exceed the maximum value, since on + // 32 bit architectures it's not possible to allocate that much memory anyway. + nint offset = (nint)(uint)this.position * (nint)(uint)this.step; + ref T ri = ref Unsafe.Add(ref r0, offset); return ref ri; -#endif } } @@ -143,10 +151,13 @@ public readonly void Clear() ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); int length = this.length; #endif + nint + step = (nint)(uint)this.step, + offset = 0; - for (int i = 0; i < length; i += this.step) + for (int i = length; i >= 0; i--, offset += step) { - Unsafe.Add(ref r0, (nint)(uint)i) = default!; + Unsafe.Add(ref r0, offset) = default!; } } @@ -173,16 +184,19 @@ public readonly void CopyTo(Span destination) ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); int length = this.length; #endif - if ((uint)destination.Length < (uint)(length / this.step)) + if ((uint)destination.Length < (uint)length) { ThrowArgumentExceptionForDestinationTooShort(); } ref T destinationRef = ref destination.DangerousGetReference(); + nint + step = (nint)(uint)this.step, + offset = 0; - for (int i = 0, j = 0; i < length; i += this.step, j++) + for (int i = 0; i < length; i++, offset += step) { - Unsafe.Add(ref destinationRef, (nint)(uint)j) = Unsafe.Add(ref sourceRef, (nint)(uint)i); + Unsafe.Add(ref destinationRef, i) = Unsafe.Add(ref sourceRef, offset); } } @@ -234,9 +248,13 @@ public readonly void Fill(T value) int length = this.length; #endif - for (int i = 0; i < length; i += this.step) + nint + step = (nint)(uint)this.step, + offset = 0; + + for (int i = length; i >= 0; i--, offset += step) { - Unsafe.Add(ref r0, (nint)(uint)i) = value; + Unsafe.Add(ref r0, offset) = value; } } @@ -254,11 +272,6 @@ public readonly void Fill(T value) public readonly T[] ToArray() { #if SPAN_RUNTIME_SUPPORT - if (this.step == 1) - { - return this.span.ToArray(); - } - int length = this.span.Length; #else int length = this.length; @@ -270,19 +283,9 @@ public readonly T[] ToArray() return Array.Empty(); } -#if SPAN_RUNTIME_SUPPORT - ref T sourceRef = ref this.span.DangerousGetReference(); -#else - ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); -#endif - - T[] array = new T[length / this.step]; - ref T destinationRef = ref array.DangerousGetReference(); + T[] array = new T[length]; - for (int i = 0, j = 0; i < length; i += this.step, j++) - { - Unsafe.Add(ref destinationRef, (nint)(uint)j) = Unsafe.Add(ref sourceRef, (nint)(uint)i); - } + CopyTo(array); return array; } From 94bd6d1891e2f6602daa01af0a3d96d1f932970b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 22 Oct 2020 15:46:39 +0200 Subject: [PATCH 158/200] Renamed Size property to Length --- .../ParallelHelper.ForEach.IInAction2D.cs | 2 +- .../ParallelHelper.ForEach.IRefAction2D.cs | 2 +- .../Memory/Memory2D{T}.cs | 4 +-- .../Memory/ReadOnlyMemory2D{T}.cs | 4 +-- .../Memory/ReadOnlySpan2D{T}.cs | 16 ++++----- .../Memory/Span2D{T}.cs | 16 ++++----- .../Test_ParallelHelper.ForEach.In2D.cs | 2 +- .../Test_ParallelHelper.ForEach.Ref2D.cs | 2 +- .../Memory/Test_Memory2D{T}.cs | 30 ++++++++-------- .../Memory/Test_ReadOnlyMemory2D{T}.cs | 26 +++++++------- .../Memory/Test_ReadOnlySpan2D{T}.cs | 30 ++++++++-------- .../Memory/Test_Span2D{T}.cs | 34 +++++++++---------- 12 files changed, 84 insertions(+), 84 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs index 096b81d4d13..c773ffa8b90 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs @@ -85,7 +85,7 @@ public static void ForEach(ReadOnlyMemory2D memory, in TA } int - maxBatches = 1 + ((memory.Size - 1) / minimumActionsPerThread), + maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread), clipBatches = Math.Min(maxBatches, memory.Height), cores = Environment.ProcessorCount, numBatches = Math.Min(clipBatches, cores), diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs index 047aa58752d..362b2b31611 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs @@ -85,7 +85,7 @@ public static void ForEach(Memory2D memory, in TAction ac } int - maxBatches = 1 + ((memory.Size - 1) / minimumActionsPerThread), + maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread), clipBatches = Math.Min(maxBatches, memory.Height), cores = Environment.ProcessorCount, numBatches = Math.Min(clipBatches, cores), diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index fc37ccfaad9..d216dee9cbb 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -567,7 +567,7 @@ public bool IsEmpty /// /// Gets the length of the current instance. /// - public int Size + public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Height * Width; @@ -771,7 +771,7 @@ public bool TryGetMemory(out Memory memory) { string text = Unsafe.As(this.instance); int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt(this.offset)); - ReadOnlyMemory temp = text.AsMemory(index, Size); + ReadOnlyMemory temp = text.AsMemory(index, Length); // The string type could still be present if a user ends up creating a // Memory2D instance from a string using DangerousCreate. Similarly to diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index c3c933ad2c1..11432418053 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -624,7 +624,7 @@ public bool IsEmpty /// /// Gets the length of the current instance. /// - public int Size + public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Height * Width; @@ -829,7 +829,7 @@ public bool TryGetMemory(out ReadOnlyMemory memory) { string text = Unsafe.As(this.instance); int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt(this.offset)); - ReadOnlyMemory temp = text.AsMemory(index, Size); + ReadOnlyMemory temp = text.AsMemory(index, Length); memory = Unsafe.As, ReadOnlyMemory>(ref temp); } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 9c6e601d6dc..2026bbc7e3b 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -540,7 +540,7 @@ public bool IsEmpty /// /// Gets the length of the current instance. /// - public int Size + public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Height * Width; @@ -653,7 +653,7 @@ public void CopyTo(Span destination) } else { - if (Size > destination.Length) + if (Length > destination.Length) { ThrowHelper.ThrowArgumentExceptionForDestinationTooShort(); } @@ -742,7 +742,7 @@ public void CopyTo(Span2D destination) /// Whether or not the operation was successful. public bool TryCopyTo(Span destination) { - if (destination.Length >= Size) + if (destination.Length >= Length) { CopyTo(destination); @@ -783,7 +783,7 @@ public unsafe ref T GetPinnableReference() { ref T r0 = ref Unsafe.AsRef(null); - if (Size != 0) + if (Length != 0) { #if SPAN_RUNTIME_SUPPORT r0 = ref MemoryMarshal.GetReference(this.span); @@ -913,7 +913,7 @@ public bool TryGetSpan(out ReadOnlySpan span) if (this.pitch == 0) { #if SPAN_RUNTIME_SUPPORT - span = MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(this.span), Size); + span = MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(this.span), Length); return true; #else @@ -930,7 +930,7 @@ public bool TryGetSpan(out ReadOnlySpan span) { unsafe { - span = new Span((void*)this.offset, Size); + span = new Span((void*)this.offset, Length); } return true; @@ -939,7 +939,7 @@ public bool TryGetSpan(out ReadOnlySpan span) // Without Span runtime support, we can only get a Span from a T[] instance if (this.instance.GetType() == typeof(T[])) { - span = Unsafe.As(this.instance).AsSpan((int)this.offset, Size); + span = Unsafe.As(this.instance).AsSpan((int)this.offset, Length); return true; } @@ -964,7 +964,7 @@ public bool TryGetSpan(out ReadOnlySpan span) CopyTo(array.AsSpan()); #else // Skip the initialization if the array is empty - if (Size > 0) + if (Length > 0) { int height = Height; nint width = (nint)(uint)this.width; diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index b1286c60a0f..09b9da24553 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -562,7 +562,7 @@ public bool IsEmpty /// /// Gets the length of the current instance. /// - public int Size + public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Height * this.width; @@ -714,7 +714,7 @@ public void CopyTo(Span destination) } else { - if (Size > destination.Length) + if (Length > destination.Length) { ThrowHelper.ThrowArgumentExceptionForDestinationTooShort(); } @@ -803,7 +803,7 @@ public void CopyTo(Span2D destination) /// Whether or not the operation was successful. public bool TryCopyTo(Span destination) { - if (destination.Length >= Size) + if (destination.Length >= Length) { CopyTo(destination); @@ -884,7 +884,7 @@ public unsafe ref T GetPinnableReference() { ref T r0 = ref Unsafe.AsRef(null); - if (Size != 0) + if (Length != 0) { #if SPAN_RUNTIME_SUPPORT r0 = ref MemoryMarshal.GetReference(this.span); @@ -1014,7 +1014,7 @@ public bool TryGetSpan(out Span span) if (this.Pitch == 0) { #if SPAN_RUNTIME_SUPPORT - span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Size); + span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Length); return true; #else @@ -1031,7 +1031,7 @@ public bool TryGetSpan(out Span span) { unsafe { - span = new Span((void*)this.Offset, Size); + span = new Span((void*)this.Offset, Length); } return true; @@ -1040,7 +1040,7 @@ public bool TryGetSpan(out Span span) // Without Span runtime support, we can only get a Span from a T[] instance if (this.Instance.GetType() == typeof(T[])) { - span = Unsafe.As(this.Instance).AsSpan((int)this.Offset, Size); + span = Unsafe.As(this.Instance).AsSpan((int)this.Offset, Length); return true; } @@ -1065,7 +1065,7 @@ public bool TryGetSpan(out Span span) CopyTo(array.AsSpan()); #else // Skip the initialization if the array is empty - if (Size > 0) + if (Length > 0) { int height = Height; nint width = (nint)(uint)this.width; diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In2D.cs index f7b7619de59..2311db56e80 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In2D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In2D.cs @@ -37,7 +37,7 @@ public unsafe void Test_ParallelHelper_ForEach_In2D( ReadOnlyMemory2D memory = data.AsMemory2D(row, column, height, width); - Assert.AreEqual(memory.Size, height * width); + Assert.AreEqual(memory.Length, height * width); Assert.AreEqual(memory.Height, height); Assert.AreEqual(memory.Width, width); diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref2D.cs index 86b93b24ffc..db4406768be 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref2D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref2D.cs @@ -40,7 +40,7 @@ public void Test_ParallelHelper_ForEach_Ref2D( Memory2D memory = data.AsMemory2D(row, column, height, width); - Assert.AreEqual(memory.Size, height * width); + Assert.AreEqual(memory.Length, height * width); Assert.AreEqual(memory.Height, height); Assert.AreEqual(memory.Width, width); diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs index 41d1bfbf423..3146e01a459 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs @@ -24,28 +24,28 @@ public void Test_Memory2DT_Empty() Memory2D empty1 = default; Assert.IsTrue(empty1.IsEmpty); - Assert.AreEqual(empty1.Size, 0); + Assert.AreEqual(empty1.Length, 0); Assert.AreEqual(empty1.Width, 0); Assert.AreEqual(empty1.Height, 0); Memory2D empty2 = Memory2D.Empty; Assert.IsTrue(empty2.IsEmpty); - Assert.AreEqual(empty2.Size, 0); + Assert.AreEqual(empty2.Length, 0); Assert.AreEqual(empty2.Width, 0); Assert.AreEqual(empty2.Height, 0); Memory2D empty3 = new int[4, 0]; Assert.IsTrue(empty3.IsEmpty); - Assert.AreEqual(empty3.Size, 0); + Assert.AreEqual(empty3.Length, 0); Assert.AreEqual(empty3.Width, 0); Assert.AreEqual(empty3.Height, 4); Memory2D empty4 = new int[0, 7]; Assert.IsTrue(empty4.IsEmpty); - Assert.AreEqual(empty4.Size, 0); + Assert.AreEqual(empty4.Length, 0); Assert.AreEqual(empty4.Width, 7); Assert.AreEqual(empty4.Height, 0); } @@ -62,7 +62,7 @@ public void Test_Memory2DT_Array1DConstructor() Memory2D memory2d = new Memory2D(array, 1, 2, 2, 1); Assert.IsFalse(memory2d.IsEmpty); - Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Length, 4); Assert.AreEqual(memory2d.Width, 2); Assert.AreEqual(memory2d.Height, 2); Assert.AreEqual(memory2d.Span[0, 0], 2); @@ -89,7 +89,7 @@ public void Test_Memory2DT_Array2DConstructor_1() Memory2D memory2d = new Memory2D(array); Assert.IsFalse(memory2d.IsEmpty); - Assert.AreEqual(memory2d.Size, 6); + Assert.AreEqual(memory2d.Length, 6); Assert.AreEqual(memory2d.Width, 3); Assert.AreEqual(memory2d.Height, 2); Assert.AreEqual(memory2d.Span[0, 1], 2); @@ -111,7 +111,7 @@ public void Test_Memory2DT_Array2DConstructor_2() Memory2D memory2d = new Memory2D(array, 0, 1, 2, 2); Assert.IsFalse(memory2d.IsEmpty); - Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Length, 4); Assert.AreEqual(memory2d.Width, 2); Assert.AreEqual(memory2d.Height, 2); Assert.AreEqual(memory2d.Span[0, 0], 2); @@ -139,7 +139,7 @@ public void Test_Memory2DT_Array3DConstructor_1() Memory2D memory2d = new Memory2D(array, 1); Assert.IsFalse(memory2d.IsEmpty); - Assert.AreEqual(memory2d.Size, 6); + Assert.AreEqual(memory2d.Length, 6); Assert.AreEqual(memory2d.Width, 3); Assert.AreEqual(memory2d.Height, 2); Assert.AreEqual(memory2d.Span[0, 1], 20); @@ -168,7 +168,7 @@ public void Test_Memory2DT_Array3DConstructor_2() Memory2D memory2d = new Memory2D(array, 1, 0, 1, 2, 2); Assert.IsFalse(memory2d.IsEmpty); - Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Length, 4); Assert.AreEqual(memory2d.Width, 2); Assert.AreEqual(memory2d.Height, 2); Assert.AreEqual(memory2d.Span[0, 0], 20); @@ -194,7 +194,7 @@ public void Test_Memory2DT_MemoryConstructor() Memory2D memory2d = memory.AsMemory2D(1, 2, 2, 1); Assert.IsFalse(memory2d.IsEmpty); - Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Length, 4); Assert.AreEqual(memory2d.Width, 2); Assert.AreEqual(memory2d.Height, 2); Assert.AreEqual(memory2d.Span[0, 0], 2); @@ -222,7 +222,7 @@ public void Test_Memory2DT_Slice_1() Memory2D slice1 = memory2d.Slice(1, 1, 1, 2); - Assert.AreEqual(slice1.Size, 2); + Assert.AreEqual(slice1.Length, 2); Assert.AreEqual(slice1.Height, 1); Assert.AreEqual(slice1.Width, 2); Assert.AreEqual(slice1.Span[0, 0], 5); @@ -230,7 +230,7 @@ public void Test_Memory2DT_Slice_1() Memory2D slice2 = memory2d.Slice(0, 1, 2, 2); - Assert.AreEqual(slice2.Size, 4); + Assert.AreEqual(slice2.Length, 4); Assert.AreEqual(slice2.Height, 2); Assert.AreEqual(slice2.Width, 2); Assert.AreEqual(slice2.Span[0, 0], 2); @@ -260,7 +260,7 @@ public void Test_Memory2DT_Slice_2() Memory2D slice1 = memory2d.Slice(0, 0, 2, 2); - Assert.AreEqual(slice1.Size, 4); + Assert.AreEqual(slice1.Length, 4); Assert.AreEqual(slice1.Height, 2); Assert.AreEqual(slice1.Width, 2); Assert.AreEqual(slice1.Span[0, 0], 1); @@ -268,7 +268,7 @@ public void Test_Memory2DT_Slice_2() Memory2D slice2 = slice1.Slice(1, 0, 1, 2); - Assert.AreEqual(slice2.Size, 2); + Assert.AreEqual(slice2.Length, 2); Assert.AreEqual(slice2.Height, 1); Assert.AreEqual(slice2.Width, 2); Assert.AreEqual(slice2.Span[0, 0], 4); @@ -276,7 +276,7 @@ public void Test_Memory2DT_Slice_2() Memory2D slice3 = slice2.Slice(0, 1, 1, 1); - Assert.AreEqual(slice3.Size, 1); + Assert.AreEqual(slice3.Length, 1); Assert.AreEqual(slice3.Height, 1); Assert.AreEqual(slice3.Width, 1); Assert.AreEqual(slice3.Span[0, 0], 5); diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs index 0ef196410e2..2bf5a4303aa 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs @@ -24,14 +24,14 @@ public void Test_ReadOnlyMemory2DT_Empty() ReadOnlyMemory2D empty1 = default; Assert.IsTrue(empty1.IsEmpty); - Assert.AreEqual(empty1.Size, 0); + Assert.AreEqual(empty1.Length, 0); Assert.AreEqual(empty1.Width, 0); Assert.AreEqual(empty1.Height, 0); ReadOnlyMemory2D empty2 = ReadOnlyMemory2D.Empty; Assert.IsTrue(empty2.IsEmpty); - Assert.AreEqual(empty2.Size, 0); + Assert.AreEqual(empty2.Length, 0); Assert.AreEqual(empty2.Width, 0); Assert.AreEqual(empty2.Height, 0); } @@ -48,7 +48,7 @@ public void Test_ReadOnlyMemory2DT_Array1DConstructor() ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array, 1, 2, 2, 1); Assert.IsFalse(memory2d.IsEmpty); - Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Length, 4); Assert.AreEqual(memory2d.Width, 2); Assert.AreEqual(memory2d.Height, 2); Assert.AreEqual(memory2d.Span[0, 0], 2); @@ -75,7 +75,7 @@ public void Test_ReadOnlyMemory2DT_Array2DConstructor_1() ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array); Assert.IsFalse(memory2d.IsEmpty); - Assert.AreEqual(memory2d.Size, 6); + Assert.AreEqual(memory2d.Length, 6); Assert.AreEqual(memory2d.Width, 3); Assert.AreEqual(memory2d.Height, 2); Assert.AreEqual(memory2d.Span[0, 1], 2); @@ -97,7 +97,7 @@ public void Test_ReadOnlyMemory2DT_Array2DConstructor_2() ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array, 0, 1, 2, 2); Assert.IsFalse(memory2d.IsEmpty); - Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Length, 4); Assert.AreEqual(memory2d.Width, 2); Assert.AreEqual(memory2d.Height, 2); Assert.AreEqual(memory2d.Span[0, 0], 2); @@ -125,7 +125,7 @@ public void Test_ReadOnlyMemory2DT_Array3DConstructor_1() ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array, 1); Assert.IsFalse(memory2d.IsEmpty); - Assert.AreEqual(memory2d.Size, 6); + Assert.AreEqual(memory2d.Length, 6); Assert.AreEqual(memory2d.Width, 3); Assert.AreEqual(memory2d.Height, 2); Assert.AreEqual(memory2d.Span[0, 1], 20); @@ -154,7 +154,7 @@ public void Test_ReadOnlyMemory2DT_Array3DConstructor_2() ReadOnlyMemory2D memory2d = new ReadOnlyMemory2D(array, 1, 0, 1, 2, 2); Assert.IsFalse(memory2d.IsEmpty); - Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Length, 4); Assert.AreEqual(memory2d.Width, 2); Assert.AreEqual(memory2d.Height, 2); Assert.AreEqual(memory2d.Span[0, 0], 20); @@ -180,7 +180,7 @@ public void Test_ReadOnlyMemory2DT_ReadOnlyMemoryConstructor() ReadOnlyMemory2D memory2d = memory.AsMemory2D(1, 2, 2, 1); Assert.IsFalse(memory2d.IsEmpty); - Assert.AreEqual(memory2d.Size, 4); + Assert.AreEqual(memory2d.Length, 4); Assert.AreEqual(memory2d.Width, 2); Assert.AreEqual(memory2d.Height, 2); Assert.AreEqual(memory2d.Span[0, 0], 2); @@ -208,7 +208,7 @@ public void Test_ReadOnlyMemory2DT_Slice_1() ReadOnlyMemory2D slice1 = memory2d.Slice(1, 1, 1, 2); - Assert.AreEqual(slice1.Size, 2); + Assert.AreEqual(slice1.Length, 2); Assert.AreEqual(slice1.Height, 1); Assert.AreEqual(slice1.Width, 2); Assert.AreEqual(slice1.Span[0, 0], 5); @@ -216,7 +216,7 @@ public void Test_ReadOnlyMemory2DT_Slice_1() ReadOnlyMemory2D slice2 = memory2d.Slice(0, 1, 2, 2); - Assert.AreEqual(slice2.Size, 4); + Assert.AreEqual(slice2.Length, 4); Assert.AreEqual(slice2.Height, 2); Assert.AreEqual(slice2.Width, 2); Assert.AreEqual(slice2.Span[0, 0], 2); @@ -246,7 +246,7 @@ public void Test_ReadOnlyMemory2DT_Slice_2() ReadOnlyMemory2D slice1 = memory2d.Slice(0, 0, 2, 2); - Assert.AreEqual(slice1.Size, 4); + Assert.AreEqual(slice1.Length, 4); Assert.AreEqual(slice1.Height, 2); Assert.AreEqual(slice1.Width, 2); Assert.AreEqual(slice1.Span[0, 0], 1); @@ -254,7 +254,7 @@ public void Test_ReadOnlyMemory2DT_Slice_2() ReadOnlyMemory2D slice2 = slice1.Slice(1, 0, 1, 2); - Assert.AreEqual(slice2.Size, 2); + Assert.AreEqual(slice2.Length, 2); Assert.AreEqual(slice2.Height, 1); Assert.AreEqual(slice2.Width, 2); Assert.AreEqual(slice2.Span[0, 0], 4); @@ -262,7 +262,7 @@ public void Test_ReadOnlyMemory2DT_Slice_2() ReadOnlyMemory2D slice3 = slice2.Slice(0, 1, 1, 1); - Assert.AreEqual(slice3.Size, 1); + Assert.AreEqual(slice3.Length, 1); Assert.AreEqual(slice3.Height, 1); Assert.AreEqual(slice3.Width, 1); Assert.AreEqual(slice3.Span[0, 0], 5); diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs index 847df116e84..3a5d445ec01 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs @@ -22,14 +22,14 @@ public void Test_ReadOnlySpan2DT_Empty() ReadOnlySpan2D empty1 = default; Assert.IsTrue(empty1.IsEmpty); - Assert.AreEqual(empty1.Size, 0); + Assert.AreEqual(empty1.Length, 0); Assert.AreEqual(empty1.Width, 0); Assert.AreEqual(empty1.Height, 0); ReadOnlySpan2D empty2 = ReadOnlySpan2D.Empty; Assert.IsTrue(empty2.IsEmpty); - Assert.AreEqual(empty2.Size, 0); + Assert.AreEqual(empty2.Length, 0); Assert.AreEqual(empty2.Width, 0); Assert.AreEqual(empty2.Height, 0); } @@ -47,7 +47,7 @@ public unsafe void Test_ReadOnlySpan2DT_RefConstructor() ReadOnlySpan2D span2d = ReadOnlySpan2D.DangerousCreate(span[0], 2, 3, 0); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Length, 6); Assert.AreEqual(span2d.Width, 3); Assert.AreEqual(span2d.Height, 2); Assert.AreEqual(span2d[0, 0], 1); @@ -76,7 +76,7 @@ public unsafe void Test_ReadOnlySpan2DT_PtrConstructor() ReadOnlySpan2D span2d = new ReadOnlySpan2D(ptr, 2, 3, 0); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Length, 6); Assert.AreEqual(span2d.Width, 3); Assert.AreEqual(span2d.Height, 2); Assert.AreEqual(span2d[0, 0], 1); @@ -100,7 +100,7 @@ public void Test_ReadOnlySpan2DT_Array1DConstructor() ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 1, 2, 2, 1); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Length, 4); Assert.AreEqual(span2d.Width, 2); Assert.AreEqual(span2d.Height, 2); Assert.AreEqual(span2d[0, 0], 2); @@ -127,7 +127,7 @@ public void Test_ReadOnlySpan2DT_Array2DConstructor_1() ReadOnlySpan2D span2d = new ReadOnlySpan2D(array); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Length, 6); Assert.AreEqual(span2d.Width, 3); Assert.AreEqual(span2d.Height, 2); Assert.AreEqual(span2d[0, 1], 2); @@ -149,7 +149,7 @@ public void Test_ReadOnlySpan2DT_Array2DConstructor_2() ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 0, 1, 2, 2); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Length, 4); Assert.AreEqual(span2d.Width, 2); Assert.AreEqual(span2d.Height, 2); Assert.AreEqual(span2d[0, 0], 2); @@ -177,7 +177,7 @@ public void Test_ReadOnlySpan2DT_Array3DConstructor_1() ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 1); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Length, 6); Assert.AreEqual(span2d.Width, 3); Assert.AreEqual(span2d.Height, 2); Assert.AreEqual(span2d[0, 0], 10); @@ -207,7 +207,7 @@ public void Test_ReadOnlySpan2DT_Array3DConstructor_2() ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 1, 0, 1, 2, 2); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Length, 4); Assert.AreEqual(span2d.Width, 2); Assert.AreEqual(span2d.Height, 2); Assert.AreEqual(span2d[0, 0], 20); @@ -405,7 +405,7 @@ public void Test_ReadOnlySpan2DT_Slice_1() ReadOnlySpan2D slice1 = span2d.Slice(1, 1, 2, 1); - Assert.AreEqual(slice1.Size, 2); + Assert.AreEqual(slice1.Length, 2); Assert.AreEqual(slice1.Height, 1); Assert.AreEqual(slice1.Width, 2); Assert.AreEqual(slice1[0, 0], 5); @@ -413,7 +413,7 @@ public void Test_ReadOnlySpan2DT_Slice_1() ReadOnlySpan2D slice2 = span2d.Slice(0, 1, 2, 2); - Assert.AreEqual(slice2.Size, 4); + Assert.AreEqual(slice2.Length, 4); Assert.AreEqual(slice2.Height, 2); Assert.AreEqual(slice2.Width, 2); Assert.AreEqual(slice2[0, 0], 2); @@ -443,7 +443,7 @@ public void Test_ReadOnlySpan2DT_Slice_2() ReadOnlySpan2D slice1 = span2d.Slice(0, 0, 2, 2); - Assert.AreEqual(slice1.Size, 4); + Assert.AreEqual(slice1.Length, 4); Assert.AreEqual(slice1.Height, 2); Assert.AreEqual(slice1.Width, 2); Assert.AreEqual(slice1[0, 0], 1); @@ -451,7 +451,7 @@ public void Test_ReadOnlySpan2DT_Slice_2() ReadOnlySpan2D slice2 = slice1.Slice(1, 0, 2, 1); - Assert.AreEqual(slice2.Size, 2); + Assert.AreEqual(slice2.Length, 2); Assert.AreEqual(slice2.Height, 1); Assert.AreEqual(slice2.Width, 2); Assert.AreEqual(slice2[0, 0], 4); @@ -459,7 +459,7 @@ public void Test_ReadOnlySpan2DT_Slice_2() ReadOnlySpan2D slice3 = slice2.Slice(0, 1, 1, 1); - Assert.AreEqual(slice3.Size, 1); + Assert.AreEqual(slice3.Length, 1); Assert.AreEqual(slice3.Height, 1); Assert.AreEqual(slice3.Width, 1); Assert.AreEqual(slice3[0, 0], 5); @@ -512,7 +512,7 @@ public void Test_ReadOnlySpan2DT_TryGetReadOnlySpan_1() Assert.AreEqual(span.Length, 0); #else Assert.IsTrue(success); - Assert.AreEqual(span.Length, span2d.Size); + Assert.AreEqual(span.Length, span2d.Length); #endif } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs index 2132fa4e134..a87428c5fd9 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -23,28 +23,28 @@ public void Test_Span2DT_Empty() Span2D empty1 = default; Assert.IsTrue(empty1.IsEmpty); - Assert.AreEqual(empty1.Size, 0); + Assert.AreEqual(empty1.Length, 0); Assert.AreEqual(empty1.Width, 0); Assert.AreEqual(empty1.Height, 0); Span2D empty2 = Span2D.Empty; Assert.IsTrue(empty2.IsEmpty); - Assert.AreEqual(empty2.Size, 0); + Assert.AreEqual(empty2.Length, 0); Assert.AreEqual(empty2.Width, 0); Assert.AreEqual(empty2.Height, 0); Span2D empty3 = new int[4, 0]; Assert.IsTrue(empty3.IsEmpty); - Assert.AreEqual(empty3.Size, 0); + Assert.AreEqual(empty3.Length, 0); Assert.AreEqual(empty3.Width, 0); Assert.AreEqual(empty3.Height, 4); Span2D empty4 = new int[0, 7]; Assert.IsTrue(empty4.IsEmpty); - Assert.AreEqual(empty4.Size, 0); + Assert.AreEqual(empty4.Length, 0); Assert.AreEqual(empty4.Width, 7); Assert.AreEqual(empty4.Height, 0); } @@ -62,7 +62,7 @@ public unsafe void Test_Span2DT_RefConstructor() Span2D span2d = Span2D.DangerousCreate(ref span[0], 2, 3, 0); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Length, 6); Assert.AreEqual(span2d.Width, 3); Assert.AreEqual(span2d.Height, 2); @@ -95,7 +95,7 @@ public unsafe void Test_Span2DT_PtrConstructor() Span2D span2d = new Span2D(ptr, 2, 3, 0); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Length, 6); Assert.AreEqual(span2d.Width, 3); Assert.AreEqual(span2d.Height, 2); @@ -123,7 +123,7 @@ public void Test_Span2DT_Array1DConstructor() Span2D span2d = new Span2D(array, 1, 2, 2, 1); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Length, 4); Assert.AreEqual(span2d.Width, 2); Assert.AreEqual(span2d.Height, 2); @@ -154,7 +154,7 @@ public void Test_Span2DT_Array2DConstructor_1() Span2D span2d = new Span2D(array); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Length, 6); Assert.AreEqual(span2d.Width, 3); Assert.AreEqual(span2d.Height, 2); @@ -180,7 +180,7 @@ public void Test_Span2DT_Array2DConstructor_2() Span2D span2d = new Span2D(array, 0, 1, 2, 2); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Length, 4); Assert.AreEqual(span2d.Width, 2); Assert.AreEqual(span2d.Height, 2); @@ -212,7 +212,7 @@ public void Test_Span2DT_Array3DConstructor_1() Span2D span2d = new Span2D(array, 1); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 6); + Assert.AreEqual(span2d.Length, 6); Assert.AreEqual(span2d.Width, 3); Assert.AreEqual(span2d.Height, 2); @@ -246,7 +246,7 @@ public void Test_Span2DT_Array3DConstructor_2() Span2D span2d = new Span2D(array, 1, 0, 1, 2, 2); Assert.IsFalse(span2d.IsEmpty); - Assert.AreEqual(span2d.Size, 4); + Assert.AreEqual(span2d.Length, 4); Assert.AreEqual(span2d.Width, 2); Assert.AreEqual(span2d.Height, 2); @@ -535,7 +535,7 @@ public void Test_Span2DT_Slice_1() Span2D slice1 = span2d.Slice(1, 1, 1, 2); - Assert.AreEqual(slice1.Size, 2); + Assert.AreEqual(slice1.Length, 2); Assert.AreEqual(slice1.Height, 1); Assert.AreEqual(slice1.Width, 2); Assert.AreEqual(slice1[0, 0], 5); @@ -543,7 +543,7 @@ public void Test_Span2DT_Slice_1() Span2D slice2 = span2d.Slice(0, 1, 2, 2); - Assert.AreEqual(slice2.Size, 4); + Assert.AreEqual(slice2.Length, 4); Assert.AreEqual(slice2.Height, 2); Assert.AreEqual(slice2.Width, 2); Assert.AreEqual(slice2[0, 0], 2); @@ -573,7 +573,7 @@ public void Test_Span2DT_Slice_2() Span2D slice1 = span2d.Slice(0, 0, 2, 2); - Assert.AreEqual(slice1.Size, 4); + Assert.AreEqual(slice1.Length, 4); Assert.AreEqual(slice1.Height, 2); Assert.AreEqual(slice1.Width, 2); Assert.AreEqual(slice1[0, 0], 1); @@ -581,7 +581,7 @@ public void Test_Span2DT_Slice_2() Span2D slice2 = slice1.Slice(1, 0, 1, 2); - Assert.AreEqual(slice2.Size, 2); + Assert.AreEqual(slice2.Length, 2); Assert.AreEqual(slice2.Height, 1); Assert.AreEqual(slice2.Width, 2); Assert.AreEqual(slice2[0, 0], 4); @@ -589,7 +589,7 @@ public void Test_Span2DT_Slice_2() Span2D slice3 = slice2.Slice(0, 1, 1, 1); - Assert.AreEqual(slice3.Size, 1); + Assert.AreEqual(slice3.Length, 1); Assert.AreEqual(slice3.Height, 1); Assert.AreEqual(slice3.Width, 1); Assert.AreEqual(slice3[0, 0], 5); @@ -642,7 +642,7 @@ public void Test_Span2DT_TryGetSpan_1() Assert.AreEqual(span.Length, 0); #else Assert.IsTrue(success); - Assert.AreEqual(span.Length, span2d.Size); + Assert.AreEqual(span.Length, span2d.Length); #endif } From c8563d7d97d974c4f1b20027d3f66006d7301711 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 22 Oct 2020 16:33:21 +0200 Subject: [PATCH 159/200] Switched [Memory|Span]2D.Length property to nint --- .../ParallelHelper.ForEach.IInAction2D.cs | 13 ++++++------ .../ParallelHelper.ForEach.IRefAction2D.cs | 20 +++++++++++++------ .../Memory/Memory2D{T}.cs | 11 +++++----- .../Memory/ReadOnlyMemory2D{T}.cs | 11 +++++----- .../Memory/ReadOnlySpan2D{T}.cs | 15 +++++++------- .../Memory/Span2D{T}.cs | 15 +++++++------- 6 files changed, 49 insertions(+), 36 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs index c773ffa8b90..2bf57ad4a21 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs @@ -84,11 +84,12 @@ public static void ForEach(ReadOnlyMemory2D memory, in TA return; } - int + nint maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread), - clipBatches = Math.Min(maxBatches, memory.Height), + clipBatches = maxBatches <= memory.Height ? maxBatches : memory.Height; + int cores = Environment.ProcessorCount, - numBatches = Math.Min(clipBatches, cores), + numBatches = (int)(clipBatches <= cores ? clipBatches : cores), batchHeight = 1 + ((memory.Height - 1) / numBatches); var actionInvoker = new InActionInvokerWithReadOnlyMemory2D(batchHeight, memory, action); @@ -134,10 +135,10 @@ public InActionInvokerWithReadOnlyMemory2D( /// The index of the batch to process public void Invoke(int i) { + int lowY = i * this.batchHeight; + nint highY = lowY + this.batchHeight; int - lowY = i * this.batchHeight, - highY = lowY + this.batchHeight, - stopY = Math.Min(highY, this.memory.Height), + stopY = (int)(highY <= this.memory.Height ? highY : this.memory.Height), width = this.memory.Width; ReadOnlySpan2D span = this.memory.Span; diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs index 362b2b31611..261e169031a 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs @@ -84,11 +84,19 @@ public static void ForEach(Memory2D memory, in TAction ac return; } - int + // The underlying data for a Memory2D instance is bound to int.MaxValue in both + // axes, but its total size can exceed this value. Because of this, we calculate + // the target chunks as nint to avoid overflows, and switch back to int values + // for the rest of the setup, since the number of batches is bound to the number + // of CPU cores (which is an int), and the height of each batch is necessarily + // smaller than or equal than int.MaxValue, as it can't be greater than the + // number of total batches, which again is capped at the number of CPU cores. + nint maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread), - clipBatches = Math.Min(maxBatches, memory.Height), + clipBatches = maxBatches <= memory.Height ? maxBatches : memory.Height; + int cores = Environment.ProcessorCount, - numBatches = Math.Min(clipBatches, cores), + numBatches = (int)(clipBatches <= cores ? clipBatches : cores), batchHeight = 1 + ((memory.Height - 1) / numBatches); var actionInvoker = new RefActionInvokerWithReadOnlyMemory2D(batchHeight, memory, action); @@ -134,10 +142,10 @@ public RefActionInvokerWithReadOnlyMemory2D( /// The index of the batch to process public void Invoke(int i) { + int lowY = i * this.batchHeight; + nint highY = lowY + this.batchHeight; int - lowY = i * this.batchHeight, - highY = lowY + this.batchHeight, - stopY = Math.Min(highY, this.memory.Height), + stopY = (int)(highY <= this.memory.Height ? highY : this.memory.Height), width = this.memory.Width; ReadOnlySpan2D span = this.memory.Span; diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index d216dee9cbb..615d5c0585f 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -567,10 +567,10 @@ public bool IsEmpty /// /// Gets the length of the current instance. /// - public int Length + public nint Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Height * Width; + get => (nint)(uint)this.height * (nint)(uint)this.width; } /// @@ -754,13 +754,14 @@ public unsafe MemoryHandle Pin() } /// - /// Tries to get a instance, if the underlying buffer is contiguous. + /// Tries to get a instance, if the underlying buffer is contiguous and small enough. /// /// The resulting , in case of success. /// Whether or not was correctly assigned. public bool TryGetMemory(out Memory memory) { - if (this.pitch == 0) + if (this.pitch == 0 && + Length <= int.MaxValue) { // Empty Memory2D instance if (this.instance is null) @@ -771,7 +772,7 @@ public bool TryGetMemory(out Memory memory) { string text = Unsafe.As(this.instance); int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt(this.offset)); - ReadOnlyMemory temp = text.AsMemory(index, Length); + ReadOnlyMemory temp = text.AsMemory(index, (int)Length); // The string type could still be present if a user ends up creating a // Memory2D instance from a string using DangerousCreate. Similarly to diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 11432418053..4580df48da9 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -624,10 +624,10 @@ public bool IsEmpty /// /// Gets the length of the current instance. /// - public int Length + public nint Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Height * Width; + get => (nint)(uint)this.height * (nint)(uint)this.width; } /// @@ -812,13 +812,14 @@ public unsafe MemoryHandle Pin() } /// - /// Tries to get a instance, if the underlying buffer is contiguous. + /// Tries to get a instance, if the underlying buffer is contiguous and small enough. /// /// The resulting , in case of success. /// Whether or not was correctly assigned. public bool TryGetMemory(out ReadOnlyMemory memory) { - if (this.pitch == 0) + if (this.pitch == 0 && + Length <= int.MaxValue) { // Empty Memory2D instance if (this.instance is null) @@ -829,7 +830,7 @@ public bool TryGetMemory(out ReadOnlyMemory memory) { string text = Unsafe.As(this.instance); int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt(this.offset)); - ReadOnlyMemory temp = text.AsMemory(index, Length); + ReadOnlyMemory temp = text.AsMemory(index, (int)Length); memory = Unsafe.As, ReadOnlyMemory>(ref temp); } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 2026bbc7e3b..b832c210c69 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -540,10 +540,10 @@ public bool IsEmpty /// /// Gets the length of the current instance. /// - public int Length + public nint Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Height * Width; + get => (nint)(uint)Height * (nint)(uint)this.width; } /// @@ -903,17 +903,18 @@ public ReadOnlySpan GetRowSpan(int row) #endif /// - /// Tries to get a instance, if the underlying buffer is contiguous. + /// Tries to get a instance, if the underlying buffer is contiguous and small enough. /// /// The resulting , in case of success. /// Whether or not was correctly assigned. public bool TryGetSpan(out ReadOnlySpan span) { // We can only create a Span if the buffer is contiguous - if (this.pitch == 0) + if (this.pitch == 0 && + Length <= int.MaxValue) { #if SPAN_RUNTIME_SUPPORT - span = MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(this.span), Length); + span = MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(this.span), (int)Length); return true; #else @@ -930,7 +931,7 @@ public bool TryGetSpan(out ReadOnlySpan span) { unsafe { - span = new Span((void*)this.offset, Length); + span = new Span((void*)this.offset, (int)Length); } return true; @@ -939,7 +940,7 @@ public bool TryGetSpan(out ReadOnlySpan span) // Without Span runtime support, we can only get a Span from a T[] instance if (this.instance.GetType() == typeof(T[])) { - span = Unsafe.As(this.instance).AsSpan((int)this.offset, Length); + span = Unsafe.As(this.instance).AsSpan((int)this.offset, (int)Length); return true; } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 09b9da24553..614a4ab09a1 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -562,10 +562,10 @@ public bool IsEmpty /// /// Gets the length of the current instance. /// - public int Length + public nint Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Height * this.width; + get => (nint)(uint)Height * (nint)(uint)this.width; } /// @@ -1004,17 +1004,18 @@ public Span GetRowSpan(int row) #endif /// - /// Tries to get a instance, if the underlying buffer is contiguous. + /// Tries to get a instance, if the underlying buffer is contiguous and small enough. /// /// The resulting , in case of success. /// Whether or not was correctly assigned. public bool TryGetSpan(out Span span) { // We can only create a Span if the buffer is contiguous - if (this.Pitch == 0) + if (this.Pitch == 0 && + Length <= int.MaxValue) { #if SPAN_RUNTIME_SUPPORT - span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), Length); + span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), (int)Length); return true; #else @@ -1031,7 +1032,7 @@ public bool TryGetSpan(out Span span) { unsafe { - span = new Span((void*)this.Offset, Length); + span = new Span((void*)this.Offset, (int)Length); } return true; @@ -1040,7 +1041,7 @@ public bool TryGetSpan(out Span span) // Without Span runtime support, we can only get a Span from a T[] instance if (this.Instance.GetType() == typeof(T[])) { - span = Unsafe.As(this.Instance).AsSpan((int)this.Offset, Length); + span = Unsafe.As(this.Instance).AsSpan((int)this.Offset, (int)Length); return true; } From 752a533c4a690dd3d7de9633597f8af19ff64548 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 22 Oct 2020 17:19:24 +0200 Subject: [PATCH 160/200] Improved support for large 2D/3D arrays --- .../Extensions/ArrayExtensions.2D.cs | 22 +++++++------- .../Extensions/ArrayExtensions.3D.cs | 29 +++++++++++-------- .../Helpers/Internals/RuntimeHelpers.cs | 19 ++++++++++++ 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 56277a3bb04..2885d6da5b6 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -12,9 +12,7 @@ using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; using Microsoft.Toolkit.HighPerformance.Memory; -#if !NETCORE_RUNTIME || SPAN_RUNTIME_SUPPORT using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; -#endif namespace Microsoft.Toolkit.HighPerformance.Extensions { @@ -66,18 +64,17 @@ 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; + nint offset = ((nint)(uint)i * (nint)(uint)arrayData.Width) + (nint)(uint)j; ref T r0 = ref Unsafe.As(ref arrayData.Data); - ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)offset); + ref T ri = ref Unsafe.Add(ref r0, offset); return ref ri; #else - int - width = array.GetLength(1), - index = (i * width) + j; + int width = array.GetLength(1); + nint index = ((nint)(uint)i * (nint)(uint)width) + (nint)(uint)j; IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset(); ref T r0 = ref array.DangerousGetObjectDataReferenceAt(offset); - ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)index); + ref T ri = ref Unsafe.Add(ref r0, index); return ref ri; #endif @@ -353,7 +350,10 @@ public static Memory AsMemory(this T[,]? array) ThrowArrayTypeMismatchException(); } - return new RawObjectMemoryManager(array, RuntimeHelpers.GetArray2DDataByteOffset(), array.Length).Memory; + IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset(); + int length = array.Length; + + return new RawObjectMemoryManager(array, offset, length).Memory; } /// @@ -396,7 +396,7 @@ public static unsafe int Count(this T[,] array, T value) where T : IEquatable { ref T r0 = ref array.DangerousGetReference(); - nint length = (nint)(uint)array.Length; + nint length = RuntimeHelpers.GetArrayNativeLength(array); return SpanHelper.Count(ref r0, length, value); } @@ -415,7 +415,7 @@ public static unsafe int GetDjb2HashCode(this T[,] array) where T : notnull { ref T r0 = ref array.DangerousGetReference(); - nint length = (nint)(uint)array.Length; + nint length = RuntimeHelpers.GetArrayNativeLength(array); return SpanHelper.GetDjb2HashCode(ref r0, length); } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index fa9738d16d4..2366299535b 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -11,9 +11,7 @@ #endif using Microsoft.Toolkit.HighPerformance.Helpers.Internals; using Microsoft.Toolkit.HighPerformance.Memory; -#if !NETCORE_RUNTIME || SPAN_RUNTIME_SUPPORT using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; -#endif namespace Microsoft.Toolkit.HighPerformance.Extensions { @@ -66,19 +64,23 @@ public static ref T DangerousGetReferenceAt(this T[,,] array, int i, int j, i { #if NETCORE_RUNTIME var arrayData = Unsafe.As(array); - int offset = (i * arrayData.Height * arrayData.Width) + (j * arrayData.Width) + k; + nint offset = + ((nint)(uint)i * (nint)(uint)arrayData.Height * (nint)(uint)arrayData.Width) + + ((nint)(uint)j * (nint)(uint)arrayData.Width) + (nint)(uint)k; ref T r0 = ref Unsafe.As(ref arrayData.Data); - ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)offset); + ref T ri = ref Unsafe.Add(ref r0, offset); return ref ri; #else int height = array.GetLength(1), - width = array.GetLength(2), - index = (i * height * width) + (j * width) + k; + width = array.GetLength(2); + nint index = + ((nint)(uint)i * (nint)(uint)height * (nint)(uint)width) + + ((nint)(uint)j * (nint)(uint)width) + (nint)(uint)k; IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset(); ref T r0 = ref array.DangerousGetObjectDataReferenceAt(offset); - ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)index); + ref T ri = ref Unsafe.Add(ref r0, index); return ref ri; #endif @@ -126,7 +128,10 @@ public static Memory AsMemory(this T[,,]? array) ThrowArrayTypeMismatchException(); } - return new RawObjectMemoryManager(array, RuntimeHelpers.GetArray3DDataByteOffset(), array.Length).Memory; + IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset(); + int length = array.Length; + + return new RawObjectMemoryManager(array, offset, length).Memory; } /// @@ -179,7 +184,7 @@ public static Span AsSpan(this T[,,] array, int depth) } ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0); - int length = array.GetLength(1) * array.GetLength(2); + int length = checked(array.GetLength(1) * array.GetLength(2)); return MemoryMarshal.CreateSpan(ref r0, length); } @@ -209,7 +214,7 @@ public static Memory AsMemory(this T[,,] array, int depth) ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0); IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0); - int length = array.GetLength(1) * array.GetLength(2); + int length = checked(array.GetLength(1) * array.GetLength(2)); return new RawObjectMemoryManager(array, offset, length).Memory; } @@ -264,7 +269,7 @@ public static int Count(this T[,,] array, T value) where T : IEquatable { ref T r0 = ref array.DangerousGetReference(); - IntPtr length = (IntPtr)array.Length; + nint length = RuntimeHelpers.GetArrayNativeLength(array); return SpanHelper.Count(ref r0, length, value); } @@ -283,7 +288,7 @@ public static int GetDjb2HashCode(this T[,,] array) where T : notnull { ref T r0 = ref array.DangerousGetReference(); - IntPtr length = (IntPtr)array.Length; + nint length = RuntimeHelpers.GetArrayNativeLength(array); return SpanHelper.GetDjb2HashCode(ref r0, length); } diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs index 1cc247961f2..0f7c53ed55d 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs @@ -22,6 +22,25 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals /// internal static class RuntimeHelpers { + /// + /// Gets the length of a given array as a native integer. + /// + /// The input instance. + /// The total length of as a native integer. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint GetArrayNativeLength(Array array) + { +#if NETSTANDARD1_4 + // .NET Standard 1.4 doesn't include the API to get the long length, so + // we just cast the length and throw in case the array is larger than + // int.MaxValue. There's not much we can do in this specific case. + return (nint)(uint)array.Length; +#else + return (nint)array.LongLength; +#endif + } + /// /// Gets the byte offset to the first element in a SZ array. /// From 1136753bce39995677e37d1c80ba4066f879a0f0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 22 Oct 2020 21:27:15 +0200 Subject: [PATCH 161/200] Added missing header --- .../Memory/Internals/OverflowHelper.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs b/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs index 6d2a088055c..642378e64e3 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs @@ -1,4 +1,8 @@ -using System; +// 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 static System.Math; From cf85cc021f90826d501eb2281fbd6d5e37eda25c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 22 Oct 2020 22:34:49 +0200 Subject: [PATCH 162/200] Fixed a bug in some RefEnumerable methods --- .../Enumerables/RefEnumerable{T}.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 327b668f8ea..a78172ef193 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -155,7 +155,7 @@ public readonly void Clear() step = (nint)(uint)this.step, offset = 0; - for (int i = length; i >= 0; i--, offset += step) + for (int i = length; i > 0; i--, offset += step) { Unsafe.Add(ref r0, offset) = default!; } @@ -252,7 +252,7 @@ public readonly void Fill(T value) step = (nint)(uint)this.step, offset = 0; - for (int i = length; i >= 0; i--, offset += step) + for (int i = length; i > 0; i--, offset += step) { Unsafe.Add(ref r0, offset) = value; } From 9ae258dd819173253176b6edbaf0a8df2690ccf1 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 23 Oct 2020 03:55:14 +0200 Subject: [PATCH 163/200] Switched Count implementation to nint --- .../Extensions/ArrayExtensions.1D.cs | 23 ++- .../Extensions/ArrayExtensions.2D.cs | 11 +- .../Extensions/ArrayExtensions.3D.cs | 11 +- .../Extensions/BoolExtensions.cs | 15 ++ .../Extensions/ReadOnlySpanExtensions.cs | 2 +- .../Extensions/SpanExtensions.cs | 2 +- .../Extensions/StringExtensions.cs | 2 +- .../Helpers/Internals/RuntimeHelpers.cs | 24 ++- .../Helpers/Internals/SpanHelper.Count.cs | 149 +++++++----------- 9 files changed, 133 insertions(+), 106 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs index d8d8b4bae8b..887cdd0f989 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs @@ -10,9 +10,7 @@ #endif using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; -#if !NETCORE_RUNTIME using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; -#endif namespace Microsoft.Toolkit.HighPerformance.Extensions { @@ -106,9 +104,16 @@ public static int Count(this T[] array, T value) where T : IEquatable { ref T r0 = ref array.DangerousGetReference(); - nint length = (nint)(uint)array.Length; + nint + length = RuntimeHelpers.GetArrayNativeLength(array), + count = SpanHelper.Count(ref r0, length, value); + + if ((nuint)count > int.MaxValue) + { + ThrowOverflowException(); + } - return SpanHelper.Count(ref r0, length, value); + return (int)count; } /// @@ -177,7 +182,7 @@ public static int GetDjb2HashCode(this T[] array) where T : notnull { ref T r0 = ref array.DangerousGetReference(); - nint length = (nint)(uint)array.Length; + nint length = RuntimeHelpers.GetArrayNativeLength(array); return SpanHelper.GetDjb2HashCode(ref r0, length); } @@ -194,5 +199,13 @@ public static bool IsCovariant(this T[] array) { return default(T) is null && array.GetType() != typeof(T[]); } + + /// + /// Throws an when the "column" parameter is invalid. + /// + public static void ThrowOverflowException() + { + throw new OverflowException(); + } } } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index 2885d6da5b6..8807c558b33 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -396,9 +396,16 @@ public static unsafe int Count(this T[,] array, T value) where T : IEquatable { ref T r0 = ref array.DangerousGetReference(); - nint length = RuntimeHelpers.GetArrayNativeLength(array); + nint + length = RuntimeHelpers.GetArrayNativeLength(array), + count = SpanHelper.Count(ref r0, length, value); + + if ((nuint)count > int.MaxValue) + { + ThrowOverflowException(); + } - return SpanHelper.Count(ref r0, length, value); + return (int)count; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index 2366299535b..846e3c5eadb 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -269,9 +269,16 @@ public static int Count(this T[,,] array, T value) where T : IEquatable { ref T r0 = ref array.DangerousGetReference(); - nint length = RuntimeHelpers.GetArrayNativeLength(array); + nint + length = RuntimeHelpers.GetArrayNativeLength(array), + count = SpanHelper.Count(ref r0, length, value); + + if ((nuint)count > int.MaxValue) + { + ThrowOverflowException(); + } - return SpanHelper.Count(ref r0, length, value); + return (int)count; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/BoolExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/BoolExtensions.cs index 19e5c258619..95040b5689c 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/BoolExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/BoolExtensions.cs @@ -2,6 +2,7 @@ // 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; @@ -12,6 +13,19 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions /// public static class BoolExtensions { + /// + /// Converts the given value into a . + /// + /// The input value to convert. + /// 1 if is , 0 otherwise. + /// This method does not contain branching instructions. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte ToByte(this bool flag) + { + return Unsafe.As(ref flag); + } + /// /// Converts the given value into an . /// @@ -20,6 +34,7 @@ public static class BoolExtensions /// This method does not contain branching instructions. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Obsolete("Use ToByte instead.")] public static int ToInt(this bool flag) { return Unsafe.As(ref flag); diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs index f674195efa5..47ed4825b48 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs @@ -266,7 +266,7 @@ public static int Count(this ReadOnlySpan span, T value) ref T r0 = ref MemoryMarshal.GetReference(span); nint length = (nint)(uint)span.Length; - return SpanHelper.Count(ref r0, length, value); + return (int)SpanHelper.Count(ref r0, length, value); } /// diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs index 90895b5dde8..31d40630a77 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs @@ -211,7 +211,7 @@ public static int Count(this Span span, T value) ref T r0 = ref MemoryMarshal.GetReference(span); nint length = (nint)(uint)span.Length; - return SpanHelper.Count(ref r0, length, value); + return (int)SpanHelper.Count(ref r0, length, value); } /// diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs index 5b9c93353ef..223480d5b06 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs @@ -96,7 +96,7 @@ public static int Count(this string text, char c) ref char r0 = ref text.DangerousGetReference(); nint length = (nint)(uint)text.Length; - return SpanHelper.Count(ref r0, length, c); + return (int)SpanHelper.Count(ref r0, length, c); } /// diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs index 0f7c53ed55d..d14bc211193 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs @@ -25,11 +25,17 @@ internal static class RuntimeHelpers /// /// Gets the length of a given array as a native integer. /// + /// The type of values in the array. /// The input instance. /// The total length of as a native integer. + /// + /// This method is needed because this expression is not inlined correctly if the target array + /// is only visible as a non-generic instance, because the C# compiler will + /// not be able to emit the opcode instead of calling the right method. + /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nint GetArrayNativeLength(Array array) + public static nint GetArrayNativeLength(T[] array) { #if NETSTANDARD1_4 // .NET Standard 1.4 doesn't include the API to get the long length, so @@ -41,6 +47,22 @@ public static nint GetArrayNativeLength(Array array) #endif } + /// + /// Gets the length of a given array as a native integer. + /// + /// The input instance. + /// The total length of as a native integer. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint GetArrayNativeLength(Array array) + { +#if NETSTANDARD1_4 + return (nint)(uint)array.Length; +#else + return (nint)array.LongLength; +#endif + } + /// /// Gets the byte offset to the first element in a SZ array. /// diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs index 8e1288bda9e..b698276b768 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs @@ -25,7 +25,7 @@ internal static partial class SpanHelper /// The number of occurrences of in the search space [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Count(ref T r0, IntPtr length, T value) + public static nint Count(ref T r0, nint length, T value) where T : IEquatable { if (!Vector.IsHardwareAccelerated) @@ -82,44 +82,44 @@ public static int Count(ref T r0, IntPtr length, T value) #if NETCOREAPP3_1 [MethodImpl(MethodImplOptions.AggressiveOptimization)] #endif - private static unsafe int CountSequential(ref T r0, IntPtr length, T value) + private static nint CountSequential(ref T r0, nint length, T value) where T : IEquatable { - int result = 0; - - IntPtr offset = default; + nint + result = 0, + offset = 0; // Main loop with 8 unrolled iterations - while ((byte*)length >= (byte*)8) + while (length >= 8) { - result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToInt(); + result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToByte(); length -= 8; offset += 8; } - if ((byte*)length >= (byte*)4) + if (length >= 4) { - result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt(); + result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte(); length -= 4; offset += 4; } // Iterate over the remaining values and count those that match - while ((byte*)length > (byte*)0) + while (length > 0) { - result += Unsafe.Add(ref r0, offset).Equals(value).ToInt(); + result += Unsafe.Add(ref r0, offset).Equals(value).ToByte(); length -= 1; offset += 1; @@ -135,15 +135,15 @@ private static unsafe int CountSequential(ref T r0, IntPtr length, T value) #if NETCOREAPP3_1 [MethodImpl(MethodImplOptions.AggressiveOptimization)] #endif - private static unsafe int CountSimd(ref T r0, IntPtr length, T value, IntPtr max) + private static nint CountSimd(ref T r0, nint length, T value, nint max) where T : unmanaged, IEquatable { - int result = 0; - - IntPtr offset = default; + nint + result = 0, + offset = 0; // Skip the initialization overhead if there are not enough items - if ((byte*)length >= (byte*)Vector.Count) + if (length >= Vector.Count) { var vc = new Vector(value); @@ -154,13 +154,13 @@ private static unsafe int CountSimd(ref T r0, IntPtr length, T value, IntPtr // to sum the partial results. We also backup the current offset to // be able to track how many items have been processed, which lets // us avoid updating a third counter (length) in the loop body. - IntPtr - chunkLength = Min(length, max), + nint + chunkLength = length <= max ? length : max, initialOffset = offset; var partials = Vector.Zero; - while ((byte*)chunkLength >= (byte*)Vector.Count) + while (chunkLength >= Vector.Count) { ref T ri = ref Unsafe.Add(ref r0, offset); @@ -181,27 +181,26 @@ private static unsafe int CountSimd(ref T r0, IntPtr length, T value, IntPtr offset += Vector.Count; } - result += CastToInt(Vector.Dot(partials, Vector.One)); - - length = Subtract(length, Subtract(offset, initialOffset)); + result += CastToNativeInt(Vector.Dot(partials, Vector.One)); + length -= offset - initialOffset; } - while ((byte*)length >= (byte*)Vector.Count); + while (length >= Vector.Count); } // Optional 8 unrolled iterations. This is only done when a single SIMD // register can contain over 8 values of the current type, as otherwise // there could never be enough items left after the vectorized path if (Vector.Count > 8 && - (byte*)length >= (byte*)8) + length >= 8) { - result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToInt(); + result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToByte(); length -= 8; offset += 8; @@ -209,21 +208,21 @@ private static unsafe int CountSimd(ref T r0, IntPtr length, T value, IntPtr // Optional 4 unrolled iterations if (Vector.Count > 4 && - (byte*)length >= (byte*)4) + length >= 4) { - result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt(); - result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt(); + result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte(); + result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte(); length -= 4; offset += 4; } // Iterate over the remaining values and count those that match - while ((byte*)length > (byte*)0) + while (length > 0) { - result += Unsafe.Add(ref r0, offset).Equals(value).ToInt(); + result += Unsafe.Add(ref r0, offset).Equals(value).ToByte(); length -= 1; offset += 1; @@ -233,70 +232,34 @@ private static unsafe int CountSimd(ref T r0, IntPtr length, T value, IntPtr } /// - /// Returns the minimum between two values. - /// - /// The first value. - /// The second value - /// The minimum between and . - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe IntPtr Min(IntPtr a, IntPtr b) - { - if (sizeof(IntPtr) == 4) - { - return (IntPtr)Math.Min((int)a, (int)b); - } - - return (IntPtr)Math.Min((long)a, (long)b); - } - - /// - /// Returns the difference between two values. - /// - /// The first value. - /// The second value - /// The difference between and . - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe IntPtr Subtract(IntPtr a, IntPtr b) - { - if (sizeof(IntPtr) == 4) - { - return (IntPtr)((int)a - (int)b); - } - - return (IntPtr)((long)a - (long)b); - } - - /// - /// Casts a value of a given type to . + /// Casts a value of a given type to a native . /// /// The input type to cast. - /// The input value to cast to . - /// The cast of . + /// The input value to cast to native . + /// The native cast of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int CastToInt(T value) + private static nint CastToNativeInt(T value) where T : unmanaged { if (typeof(T) == typeof(sbyte)) { - return Unsafe.As(ref value); + return Unsafe.As(ref value); } if (typeof(T) == typeof(short)) { - return Unsafe.As(ref value); + return Unsafe.As(ref value); } if (typeof(T) == typeof(int)) { - return Unsafe.As(ref value); + return (nint)Unsafe.As(ref value); } if (typeof(T) == typeof(long)) { - return (int)Unsafe.As(ref value); + return (nint)Unsafe.As(ref value); } throw new NotSupportedException($"Invalid input type {typeof(T)}"); From 00e2645ff084515247c0e602ee8e74112c167411 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 23 Oct 2020 04:00:05 +0200 Subject: [PATCH 164/200] Code refactoring with IntPtr to nint --- .../Helpers/Internals/SpanHelper.Hash.cs | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Hash.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Hash.cs index 9ad86f923e0..a7920734683 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Hash.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Hash.cs @@ -2,7 +2,6 @@ // 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.Numerics; using System.Runtime.CompilerServices; @@ -25,14 +24,13 @@ internal static partial class SpanHelper #if NETCOREAPP3_1 [MethodImpl(MethodImplOptions.AggressiveOptimization)] #endif - public static unsafe int GetDjb2HashCode(ref T r0, IntPtr length) + public static int GetDjb2HashCode(ref T r0, nint length) where T : notnull { int hash = 5381; + nint offset = 0; - IntPtr offset = default; - - while ((byte*)length >= (byte*)8) + while (length >= 8) { // Doing a left shift by 5 and adding is equivalent to multiplying by 33. // This is preferred for performance reasons, as when working with integer @@ -52,7 +50,7 @@ public static unsafe int GetDjb2HashCode(ref T r0, IntPtr length) offset += 8; } - if ((byte*)length >= (byte*)4) + if (length >= 4) { hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 0).GetHashCode()); hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 1).GetHashCode()); @@ -63,7 +61,7 @@ public static unsafe int GetDjb2HashCode(ref T r0, IntPtr length) offset += 4; } - while ((byte*)length > (byte*)0) + while (length > 0) { hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset).GetHashCode()); @@ -92,11 +90,10 @@ public static unsafe int GetDjb2HashCode(ref T r0, IntPtr length) #if NETCOREAPP3_1 [MethodImpl(MethodImplOptions.AggressiveOptimization)] #endif - public static unsafe int GetDjb2LikeByteHash(ref byte r0, IntPtr length) + public static unsafe int GetDjb2LikeByteHash(ref byte r0, nint length) { int hash = 5381; - - IntPtr offset = default; + nint offset = 0; // Check whether SIMD instructions are supported, and also check // whether we have enough data to perform at least one unrolled @@ -107,7 +104,7 @@ public static unsafe int GetDjb2LikeByteHash(ref byte r0, IntPtr length) // any preprocessing to try to get memory aligned, as that would cause // the hash codes to potentially be different for the same data. if (Vector.IsHardwareAccelerated && - (byte*)length >= (byte*)(Vector.Count << 3)) + length >= (Vector.Count << 3)) { var vh = new Vector(5381); var v33 = new Vector(33); @@ -115,7 +112,7 @@ public static unsafe int GetDjb2LikeByteHash(ref byte r0, IntPtr length) // First vectorized loop, with 8 unrolled iterations. // Assuming 256-bit registers (AVX2), a total of 256 bytes are processed // per iteration, with the partial hashes being accumulated for later use. - while ((byte*)length >= (byte*)(Vector.Count << 3)) + while (length >= (Vector.Count << 3)) { ref byte ri0 = ref Unsafe.Add(ref r0, offset + (Vector.Count * 0)); var vi0 = Unsafe.ReadUnaligned>(ref ri0); @@ -163,7 +160,7 @@ public static unsafe int GetDjb2LikeByteHash(ref byte r0, IntPtr length) // When this loop is reached, there are up to 255 bytes left (on AVX2). // Each iteration processed an additional 32 bytes and accumulates the results. - while ((byte*)length >= (byte*)Vector.Count) + while (length >= Vector.Count) { ref byte ri = ref Unsafe.Add(ref r0, offset); var vi = Unsafe.ReadUnaligned>(ref ri); @@ -186,9 +183,9 @@ public static unsafe int GetDjb2LikeByteHash(ref byte r0, IntPtr length) // Only use the loop working with 64-bit values if we are on a // 64-bit processor, otherwise the result would be much slower. // Each unrolled iteration processes 64 bytes. - if (sizeof(IntPtr) == sizeof(ulong)) + if (sizeof(nint) == sizeof(ulong)) { - while ((byte*)length >= (byte*)(sizeof(ulong) << 3)) + while (length >= (sizeof(ulong) << 3)) { ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 0)); var value0 = Unsafe.ReadUnaligned(ref ri0); @@ -228,7 +225,7 @@ public static unsafe int GetDjb2LikeByteHash(ref byte r0, IntPtr length) } // Each unrolled iteration processes 32 bytes - while ((byte*)length >= (byte*)(sizeof(uint) << 3)) + while (length >= (sizeof(uint) << 3)) { ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 0)); var value0 = Unsafe.ReadUnaligned(ref ri0); @@ -271,7 +268,7 @@ public static unsafe int GetDjb2LikeByteHash(ref byte r0, IntPtr length) // left, both for the vectorized and non vectorized paths. // That number would go up to 63 on AVX512 systems, in which case it is // still useful to perform this last loop unrolling. - if ((byte*)length >= (byte*)(sizeof(ushort) << 3)) + if (length >= (sizeof(ushort) << 3)) { ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 0)); var value0 = Unsafe.ReadUnaligned(ref ri0); @@ -310,7 +307,7 @@ public static unsafe int GetDjb2LikeByteHash(ref byte r0, IntPtr length) } // Handle the leftover items - while ((byte*)length > (byte*)0) + while (length > 0) { hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset)); From 3e8aae836d9406fc114f9a8721385908aa432002 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 23 Oct 2020 16:23:54 +0200 Subject: [PATCH 165/200] Minor codegen improvements --- .../Helpers/Internals/SpanHelper.Count.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs index b698276b768..74e54423a64 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs @@ -244,25 +244,25 @@ private static nint CastToNativeInt(T value) { if (typeof(T) == typeof(sbyte)) { - return Unsafe.As(ref value); + return (byte)(sbyte)(object)value; } if (typeof(T) == typeof(short)) { - return Unsafe.As(ref value); + return (ushort)(short)(object)value; } if (typeof(T) == typeof(int)) { - return (nint)Unsafe.As(ref value); + return (nint)(uint)(int)(object)value; } if (typeof(T) == typeof(long)) { - return (nint)Unsafe.As(ref value); + return (nint)(ulong)(long)(object)value; } - throw new NotSupportedException($"Invalid input type {typeof(T)}"); + throw null!; } } } From fb6ae895d136bdff198ebe5b979e96ed53736da8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 23 Oct 2020 16:33:28 +0200 Subject: [PATCH 166/200] More codegen improvements --- .../Helpers/Internals/SpanHelper.Count.cs | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs index 74e54423a64..9aba33c8150 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs @@ -41,7 +41,7 @@ public static nint Count(ref T r0, nint length, T value) ref sbyte r1 = ref Unsafe.As(ref r0); sbyte target = Unsafe.As(ref value); - return CountSimd(ref r1, length, target, (IntPtr)sbyte.MaxValue); + return CountSimd(ref r1, length, target); } if (typeof(T) == typeof(char) || @@ -51,7 +51,7 @@ public static nint Count(ref T r0, nint length, T value) ref short r1 = ref Unsafe.As(ref r0); short target = Unsafe.As(ref value); - return CountSimd(ref r1, length, target, (IntPtr)short.MaxValue); + return CountSimd(ref r1, length, target); } if (typeof(T) == typeof(int) || @@ -60,7 +60,7 @@ public static nint Count(ref T r0, nint length, T value) ref int r1 = ref Unsafe.As(ref r0); int target = Unsafe.As(ref value); - return CountSimd(ref r1, length, target, (IntPtr)int.MaxValue); + return CountSimd(ref r1, length, target); } if (typeof(T) == typeof(long) || @@ -69,7 +69,7 @@ public static nint Count(ref T r0, nint length, T value) ref long r1 = ref Unsafe.As(ref r0); long target = Unsafe.As(ref value); - return CountSimd(ref r1, length, target, (IntPtr)int.MaxValue); + return CountSimd(ref r1, length, target); } return CountSequential(ref r0, length, value); @@ -135,7 +135,7 @@ private static nint CountSequential(ref T r0, nint length, T value) #if NETCOREAPP3_1 [MethodImpl(MethodImplOptions.AggressiveOptimization)] #endif - private static nint CountSimd(ref T r0, nint length, T value, nint max) + private static nint CountSimd(ref T r0, nint length, T value) where T : unmanaged, IEquatable { nint @@ -155,6 +155,7 @@ private static nint CountSimd(ref T r0, nint length, T value, nint max) // be able to track how many items have been processed, which lets // us avoid updating a third counter (length) in the loop body. nint + max = GetUpperBound(), chunkLength = length <= max ? length : max, initialOffset = offset; @@ -231,6 +232,41 @@ private static nint CountSimd(ref T r0, nint length, T value, nint max) return result; } + /// + /// Gets the upper bound for partial sums with a given parameter. + /// + /// The type argument currently in use. + /// The native value representing the upper bound. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nint GetUpperBound() + where T : unmanaged + { + if (typeof(T) == typeof(byte) || + typeof(T) == typeof(sbyte) || + typeof(T) == typeof(bool)) + { + return sbyte.MaxValue; + } + + if (typeof(T) == typeof(char) || + typeof(T) == typeof(ushort) || + typeof(T) == typeof(short)) + { + return short.MaxValue; + } + + if (typeof(T) == typeof(int) || + typeof(T) == typeof(uint) || + typeof(T) == typeof(long) || + typeof(T) == typeof(ulong)) + { + return int.MaxValue; + } + + throw null!; + } + /// /// Casts a value of a given type to a native . /// From 4911a8696b87109be0842fb70dc6b14a98f0c10a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 23 Oct 2020 17:50:06 +0200 Subject: [PATCH 167/200] Increased count batch size on x64 for long/ulong values --- .../Helpers/Internals/SpanHelper.Count.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs index 9aba33c8150..408b3073cf1 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs @@ -239,7 +239,7 @@ private static nint CountSimd(ref T r0, nint length, T value) /// The native value representing the upper bound. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static nint GetUpperBound() + private static unsafe nint GetUpperBound() where T : unmanaged { if (typeof(T) == typeof(byte) || @@ -257,13 +257,29 @@ private static nint GetUpperBound() } if (typeof(T) == typeof(int) || - typeof(T) == typeof(uint) || - typeof(T) == typeof(long) || - typeof(T) == typeof(ulong)) + typeof(T) == typeof(uint)) { return int.MaxValue; } + if (typeof(T) == typeof(long) || + typeof(T) == typeof(ulong)) + { + if (sizeof(nint) == sizeof(int)) + { + return int.MaxValue; + } + + // If we are on a 64 bit architecture and we are counting with a SIMD vector of 64 + // bit values, we can use long.MaxValue as the upper bound, as a native integer will + // be able to contain such a value with no overflows. This will allow the count tight + // loop to process all the items in the target area in a single pass (except the mod). + // The (void*) cast is necessary to ensure the right constant is produced on runtimes + // before .NET 5 that don't natively support C# 9. For instance, removing that (void*) + // cast results in the value 0xFFFFFFFFFFFFFFFF (-1) instead of 0x7FFFFFFFFFFFFFFFF. + return (nint)(void*)long.MaxValue; + } + throw null!; } From 7d111d76e0afe5f544b321c69ce2bbbd7a25f3c3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 23 Oct 2020 18:09:47 +0200 Subject: [PATCH 168/200] Updated C# version in .csproj --- .../Microsoft.Toolkit.HighPerformance.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj index df03da93cd0..8ec27230fc6 100644 --- a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj +++ b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj @@ -2,7 +2,7 @@ netstandard1.4;netstandard2.0;netstandard2.1;netcoreapp2.1;netcoreapp3.1 - preview + 9.0 enable true Windows Community Toolkit High Performance .NET Standard From 9a10a5afccdea1d0cf26690576bda28371b29a2b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 25 Oct 2020 01:25:28 +0200 Subject: [PATCH 169/200] Minor codegen improvement --- .../Memory/ReadOnlySpan2D{T}.Enumerator.cs | 4 ++-- .../Memory/Span2D{T}.Enumerator.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs index 3a2a9ef783a..09fad84c9d1 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs @@ -172,9 +172,9 @@ public bool MoveNext() this.x = 0; #if SPAN_RUNTIME_SUPPORT - return this.y++ < (this.span.Length - 1); + return ++this.y < this.span.Length; #else - return this.y++ < this.height - 1; + return ++this.y < this.height; #endif } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index 34f7d93c51b..ee212aea9c8 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -172,9 +172,9 @@ public bool MoveNext() this.x = 0; #if SPAN_RUNTIME_SUPPORT - return this.y++ < (this.span.Length - 1); + return ++this.y < this.span.Length; #else - return this.y++ < this.height - 1; + return ++this.y < this.height; #endif } From e054aba8fc3ae3c4c5af69e463fb8bb1821d5235 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 25 Oct 2020 15:40:04 +0100 Subject: [PATCH 170/200] Minor code tweaks --- .../Memory/ReadOnlySpan2D{T}.cs | 6 ++---- Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index b832c210c69..0e37957c6a2 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -894,11 +894,9 @@ public ReadOnlySpan GetRowSpan(int row) ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); } - nint offset = (nint)(uint)(this.width + this.pitch) * (nint)(uint)row; - ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T r1 = ref Unsafe.Add(ref r0, offset); + ref T r0 = ref DangerousGetReferenceAt(row, 0); - return MemoryMarshal.CreateReadOnlySpan(ref r1, this.width); + return MemoryMarshal.CreateReadOnlySpan(ref r0, this.width); } #endif diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 614a4ab09a1..eb52ea0d44c 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -995,11 +995,9 @@ public Span GetRowSpan(int row) ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); } - nint offset = (nint)(uint)(this.width + this.Pitch) * (nint)(uint)row; - ref T r0 = ref MemoryMarshal.GetReference(this.span); - ref T r1 = ref Unsafe.Add(ref r0, offset); + ref T r0 = ref DangerousGetReferenceAt(row, 0); - return MemoryMarshal.CreateSpan(ref r1, this.width); + return MemoryMarshal.CreateSpan(ref r0, this.width); } #endif From e9e8e7ba4f6ea42b424c7bb85a1aae5a3933e5b3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 25 Oct 2020 16:42:26 +0100 Subject: [PATCH 171/200] Updated unit tests --- .../Extensions/Test_BoolExtensions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_BoolExtensions.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_BoolExtensions.cs index bc99e19d7f6..f5b3db0a9f9 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_BoolExtensions.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_BoolExtensions.cs @@ -15,16 +15,16 @@ public class Test_BoolExtensions [TestMethod] public void Test_BoolExtensions_True() { - Assert.AreEqual(1, true.ToInt(), nameof(Test_BoolExtensions_True)); - Assert.AreEqual(1, (DateTime.Now.Year > 0).ToInt(), nameof(Test_BoolExtensions_True)); + Assert.AreEqual(1, true.ToByte(), nameof(Test_BoolExtensions_True)); + Assert.AreEqual(1, (DateTime.Now.Year > 0).ToByte(), nameof(Test_BoolExtensions_True)); } [TestCategory("BoolExtensions")] [TestMethod] public void Test_BoolExtensions_False() { - Assert.AreEqual(0, false.ToInt(), nameof(Test_BoolExtensions_False)); - Assert.AreEqual(0, (DateTime.Now.Year > 3000).ToInt(), nameof(Test_BoolExtensions_False)); + Assert.AreEqual(0, false.ToByte(), nameof(Test_BoolExtensions_False)); + Assert.AreEqual(0, (DateTime.Now.Year > 3000).ToByte(), nameof(Test_BoolExtensions_False)); } [TestCategory("BoolExtensions")] From b5f3ef559e0b16b7c427083b4e31ee14bed80abd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 26 Oct 2020 13:52:30 +0100 Subject: [PATCH 172/200] Added missing overflow check, improved docs --- Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs | 2 ++ .../Memory/ReadOnlyMemory2D{T}.cs | 2 ++ .../Memory/ReadOnlySpan2D{T}.cs | 2 ++ Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs | 6 +++++- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 615d5c0585f..12c4803053e 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -545,6 +545,8 @@ public static Memory2D DangerousCreate(object instance, ref T value, int heig ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } + OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch); + IntPtr offset = instance.DangerousGetObjectDataByteOffset(ref value); return new Memory2D(instance, offset, height, width, pitch); diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 4580df48da9..652991e592f 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -602,6 +602,8 @@ public static ReadOnlyMemory2D DangerousCreate(object instance, ref T value, ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } + OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch); + IntPtr offset = instance.DangerousGetObjectDataByteOffset(ref value); return new ReadOnlyMemory2D(instance, offset, height, width, pitch); diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 0e37957c6a2..987ae848aea 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -519,6 +519,8 @@ public static ReadOnlySpan2D DangerousCreate(in T value, int height, int widt ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } + OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch); + return new ReadOnlySpan2D(value, height, width, pitch); } #endif diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index eb52ea0d44c..2032ea2117b 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -35,7 +35,8 @@ public readonly ref partial struct Span2D // row-major order as usual, and the 'XX' grid cells represent // locations that are mapped by a given Span2D instance: // - // reference__ _________width_________ ________... + // _____________________stride_____... + // reference__ /________width_________ ________... // \/ \/ // | -- | -- | |- | -- | -- | -- | -- | -- | -- | -- |_ // | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | | @@ -45,6 +46,7 @@ public readonly ref partial struct Span2D // | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | // | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | // ...__pitch__/ + // ...________/ // // The pitch is used to calculate the offset between each // discontiguous row, so that any arbitrary memory locations @@ -541,6 +543,8 @@ public static Span2D DangerousCreate(ref T value, int height, int width, int ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch(); } + OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch); + return new Span2D(ref value, height, width, pitch); } #endif From 67ec2404a34006e7949cb08b7bddd940acbd3294 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 26 Oct 2020 16:20:07 +0100 Subject: [PATCH 173/200] 15% speedup in Span2D.DangerousGetReferenceAt --- .../Memory/ReadOnlySpan2D{T}.Enumerator.cs | 14 +++---- .../Memory/ReadOnlySpan2D{T}.cs | 38 +++++++++---------- .../Memory/Span2D{T}.Enumerator.cs | 14 +++---- .../Memory/Span2D{T}.cs | 38 ++++++++++--------- 4 files changed, 52 insertions(+), 52 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs index 09fad84c9d1..30dfcbc6406 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs @@ -33,7 +33,7 @@ public ReadOnlyRefEnumerable GetRow(int row) ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); } - nint startIndex = (nint)(uint)(this.width + this.pitch) * (nint)(uint)row; + nint startIndex = (nint)(uint)this.stride * (nint)(uint)row; ref T r0 = ref DangerousGetReference(); ref T r1 = ref Unsafe.Add(ref r0, startIndex); @@ -65,11 +65,11 @@ public ReadOnlyRefEnumerable GetColumn(int column) ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column); #if SPAN_RUNTIME_SUPPORT - return new ReadOnlyRefEnumerable(r1, Height, this.width + this.pitch); + return new ReadOnlyRefEnumerable(r1, Height, this.stride); #else IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1); - return new ReadOnlyRefEnumerable(this.instance!, offset, Height, this.width + this.pitch); + return new ReadOnlyRefEnumerable(this.instance!, offset, Height, this.stride); #endif } @@ -117,9 +117,9 @@ public ref struct Enumerator private readonly int width; /// - /// The pitch of the specified 2D region. + /// The stride of the specified 2D region. /// - private readonly int pitch; + private readonly int stride; /// /// The current horizontal offset. @@ -145,7 +145,7 @@ internal Enumerator(ReadOnlySpan2D span) this.height = span.height; #endif this.width = span.width; - this.pitch = span.pitch; + this.stride = span.stride; this.x = -1; this.y = 0; } @@ -191,7 +191,7 @@ public readonly ref readonly T Current #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif - nint index = ((nint)(uint)this.y * (nint)(uint)(this.width + this.pitch)) + (nint)(uint)this.x; + nint index = ((nint)(uint)this.y * (nint)(uint)this.stride) + (nint)(uint)this.x; return ref Unsafe.Add(ref r0, index); } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 987ae848aea..30560a42dee 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -53,9 +53,9 @@ public readonly ref partial struct ReadOnlySpan2D private readonly int width; /// - /// The pitch of the specified 2D region. + /// The stride of the specified 2D region. /// - private readonly int pitch; + private readonly int stride; #if SPAN_RUNTIME_SUPPORT /// @@ -70,7 +70,7 @@ internal ReadOnlySpan2D(in T value, int height, int width, int pitch) { this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(value), height); this.width = width; - this.pitch = pitch; + this.stride = width + pitch; } #endif @@ -114,7 +114,7 @@ public unsafe ReadOnlySpan2D(void* pointer, int height, int width, int pitch) this.height = height; #endif this.width = width; - this.pitch = pitch; + this.stride = width + pitch; } #if !SPAN_RUNTIME_SUPPORT @@ -133,7 +133,7 @@ internal ReadOnlySpan2D(object? instance, IntPtr offset, int height, int width, this.offset = offset; this.height = height; this.width = width; - this.pitch = pitch; + this.stride = width + pitch; } #endif @@ -223,7 +223,7 @@ public ReadOnlySpan2D(T[] array, int offset, int height, int width, int pitch) this.height = height; #endif this.width = width; - this.pitch = pitch; + this.stride = width + pitch; } /// @@ -254,8 +254,7 @@ public ReadOnlySpan2D(T[,]? array) this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(0, 0)); this.height = array.GetLength(0); #endif - this.width = array.GetLength(1); - this.pitch = 0; + this.width = this.stride = array.GetLength(1); } /// @@ -324,7 +323,7 @@ public ReadOnlySpan2D(T[,]? array, int row, int column, int height, int width) this.height = height; #endif this.width = width; - this.pitch = columns - width; + this.stride = columns; } /// @@ -355,8 +354,7 @@ public ReadOnlySpan2D(T[,,] array, int depth) this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0)); this.height = array.GetLength(1); #endif - this.width = array.GetLength(2); - this.pitch = 0; + this.width = this.stride = array.GetLength(2); } /// @@ -416,7 +414,7 @@ public ReadOnlySpan2D(T[,,] array, int depth, int row, int column, int height, i this.height = height; #endif this.width = width; - this.pitch = columns - width; + this.stride = columns; } #if SPAN_RUNTIME_SUPPORT @@ -489,7 +487,7 @@ internal ReadOnlySpan2D(ReadOnlySpan span, int offset, int height, int width, this.span = MemoryMarshal.CreateSpan(ref span.DangerousGetReferenceAt(offset), height); this.width = width; - this.pitch = pitch; + this.stride = width + pitch; } /// @@ -827,7 +825,7 @@ public ref T DangerousGetReferenceAt(int i, int j) #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif - nint index = ((nint)(uint)i * (nint)(uint)(this.width + this.pitch)) + (nint)(uint)j; + nint index = ((nint)(uint)i * (nint)(uint)this.stride) + (nint)(uint)j; return ref Unsafe.Add(ref r0, index); } @@ -867,8 +865,8 @@ public ReadOnlySpan2D Slice(int row, int column, int width, int height) ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight(); } - nint shift = ((nint)(uint)(this.width + this.pitch) * (nint)(uint)row) + (nint)(uint)column; - int pitch = this.pitch + (this.width - width); + nint shift = ((nint)(uint)this.stride * (nint)(uint)row) + (nint)(uint)column; + int pitch = this.stride - width; #if SPAN_RUNTIME_SUPPORT ref T r0 = ref this.span.DangerousGetReferenceAt(shift); @@ -910,7 +908,7 @@ public ReadOnlySpan GetRowSpan(int row) public bool TryGetSpan(out ReadOnlySpan span) { // We can only create a Span if the buffer is contiguous - if (this.pitch == 0 && + if (this.stride == this.width && Length <= int.MaxValue) { #if SPAN_RUNTIME_SUPPORT @@ -1029,7 +1027,7 @@ public override string ToString() left.height == right.height && #endif left.width == right.width && - left.pitch == right.pitch; + left.stride == right.stride; } /// @@ -1056,9 +1054,9 @@ public override string ToString() public static implicit operator ReadOnlySpan2D(Span2D span) { #if SPAN_RUNTIME_SUPPORT - return new ReadOnlySpan2D(span.DangerousGetReference(), span.Height, span.Width, span.Pitch); + return new ReadOnlySpan2D(span.DangerousGetReference(), span.Height, span.Width, span.Stride - span.Width); #else - return new ReadOnlySpan2D(span.Instance!, span.Offset, span.Height, span.Width, span.Pitch); + return new ReadOnlySpan2D(span.Instance!, span.Offset, span.Height, span.Width, span.Stride - span.Width); #endif } } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index ee212aea9c8..fc5baf9b930 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -33,7 +33,7 @@ public RefEnumerable GetRow(int row) ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow(); } - nint startIndex = (nint)(uint)(this.width + this.Pitch) * (nint)(uint)row; + nint startIndex = (nint)(uint)this.Stride * (nint)(uint)row; ref T r0 = ref DangerousGetReference(); ref T r1 = ref Unsafe.Add(ref r0, startIndex); @@ -65,11 +65,11 @@ public RefEnumerable GetColumn(int column) ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column); #if SPAN_RUNTIME_SUPPORT - return new RefEnumerable(ref r1, Height, this.width + this.Pitch); + return new RefEnumerable(ref r1, Height, this.Stride); #else IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.Instance, ref r1); - return new RefEnumerable(this.Instance, offset, Height, this.width + this.Pitch); + return new RefEnumerable(this.Instance, offset, Height, this.Stride); #endif } @@ -117,9 +117,9 @@ public ref struct Enumerator private readonly int width; /// - /// The pitch of the specified 2D region. + /// The stride of the specified 2D region. /// - private readonly int pitch; + private readonly int stride; /// /// The current horizontal offset. @@ -145,7 +145,7 @@ internal Enumerator(Span2D span) this.height = span.height; #endif this.width = span.width; - this.pitch = span.Pitch; + this.stride = span.Stride; this.x = -1; this.y = 0; } @@ -191,7 +191,7 @@ public readonly ref T Current #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif - nint index = ((nint)(uint)this.y * (nint)(uint)(this.width + this.pitch)) + (nint)(uint)this.x; + nint index = ((nint)(uint)this.y * (nint)(uint)this.stride) + (nint)(uint)this.x; return ref Unsafe.Add(ref r0, index); } diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 2032ea2117b..2f7be520c0e 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -84,9 +84,13 @@ public readonly ref partial struct Span2D private readonly int width; /// - /// The pitch of the specified 2D region. + /// The stride of the specified 2D region. /// - internal readonly int Pitch; + /// + /// This combines both the width and pitch in a single value so that the indexing + /// logic can be simplified (no need to recompute the sum every time) and be faster. + /// + internal readonly int Stride; #if SPAN_RUNTIME_SUPPORT /// @@ -101,7 +105,7 @@ internal Span2D(ref T value, int height, int width, int pitch) { this.span = MemoryMarshal.CreateSpan(ref value, height); this.width = width; - this.Pitch = pitch; + this.Stride = width + pitch; } #endif @@ -145,7 +149,7 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) this.height = height; #endif this.width = width; - this.Pitch = pitch; + this.Stride = width + pitch; } #if !SPAN_RUNTIME_SUPPORT @@ -164,7 +168,7 @@ internal Span2D(object? instance, IntPtr offset, int height, int width, int pitc this.Offset = offset; this.height = height; this.width = width; - this.Pitch = pitch; + this.Stride = width + pitch; } #endif @@ -247,7 +251,7 @@ public Span2D(T[] array, int offset, int height, int width, int pitch) this.height = height; #endif this.width = width; - this.Pitch = pitch; + this.Stride = width + pitch; } /// @@ -278,8 +282,7 @@ public Span2D(T[,]? array) this.Offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(0, 0)); this.height = array.GetLength(0); #endif - this.width = array.GetLength(1); - this.Pitch = 0; + this.width = this.Stride = array.GetLength(1); } /// @@ -348,7 +351,7 @@ public Span2D(T[,]? array, int row, int column, int height, int width) this.height = height; #endif this.width = width; - this.Pitch = columns - width; + this.Stride = columns; } /// @@ -379,8 +382,7 @@ public Span2D(T[,,] array, int depth) this.Offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0)); this.height = array.GetLength(1); #endif - this.width = array.GetLength(2); - this.Pitch = 0; + this.width = this.Stride = array.GetLength(2); } /// @@ -440,7 +442,7 @@ public Span2D(T[,,] array, int depth, int row, int column, int height, int width this.height = height; #endif this.width = width; - this.Pitch = columns - width; + this.Stride = columns; } #if SPAN_RUNTIME_SUPPORT @@ -513,7 +515,7 @@ internal Span2D(Span span, int offset, int height, int width, int pitch) this.span = MemoryMarshal.CreateSpan(ref span.DangerousGetReferenceAt(offset), height); this.width = width; - this.Pitch = pitch; + this.Stride = width + pitch; } /// @@ -930,7 +932,7 @@ public ref T DangerousGetReferenceAt(int i, int j) #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); #endif - nint index = ((nint)(uint)i * (nint)(uint)(this.width + this.Pitch)) + (nint)(uint)j; + nint index = ((nint)(uint)i * (nint)(uint)this.Stride) + (nint)(uint)j; return ref Unsafe.Add(ref r0, index); } @@ -970,8 +972,8 @@ public Span2D Slice(int row, int column, int height, int width) ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } - nint shift = ((nint)(uint)(this.width + this.Pitch) * (nint)(uint)row) + (nint)(uint)column; - int pitch = this.Pitch + (this.width - width); + nint shift = ((nint)(uint)this.Stride * (nint)(uint)row) + (nint)(uint)column; + int pitch = this.Stride - width; #if SPAN_RUNTIME_SUPPORT ref T r0 = ref this.span.DangerousGetReferenceAt(shift); @@ -1013,7 +1015,7 @@ public Span GetRowSpan(int row) public bool TryGetSpan(out Span span) { // We can only create a Span if the buffer is contiguous - if (this.Pitch == 0 && + if (this.Stride == this.width && Length <= int.MaxValue) { #if SPAN_RUNTIME_SUPPORT @@ -1132,7 +1134,7 @@ public override string ToString() left.height == right.height && #endif left.width == right.width && - left.Pitch == right.Pitch; + left.Stride == right.Stride; } /// From b75f2381adbc45207b8258dc21cf432eaadc7e2d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 28 Oct 2020 14:52:03 +0100 Subject: [PATCH 174/200] Added more comments, minor code refactoring --- .../Extensions/ReadOnlySpanExtensions.cs | 26 +++++-------------- .../Extensions/SpanExtensions.cs | 26 +++++-------------- .../Memory/ReadOnlyMemory2D{T}.cs | 7 +++++ 3 files changed, 19 insertions(+), 40 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs index 47ed4825b48..7f7e83a07fc 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs @@ -221,34 +221,20 @@ public static ReadOnlySpan2D AsSpan2D(this ReadOnlySpan span, int offse /// Thrown if does not belong to . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe int IndexOf(this ReadOnlySpan span, in T value) + 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); - if (sizeof(IntPtr) == sizeof(long)) - { - long elementOffset = (long)byteOffset / Unsafe.SizeOf(); - - if ((ulong)elementOffset >= (ulong)span.Length) - { - SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference(); - } + nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf(); - return unchecked((int)elementOffset); - } - else + if ((nuint)elementOffset >= (uint)span.Length) { - int elementOffset = (int)byteOffset / Unsafe.SizeOf(); - - if ((uint)elementOffset >= (uint)span.Length) - { - SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference(); - } - - return elementOffset; + SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference(); } + + return (int)elementOffset; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs index 31d40630a77..7121c5f747e 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs @@ -167,33 +167,19 @@ public static Span Cast(this Span span) /// Thrown if does not belong to . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe int IndexOf(this Span span, ref T value) + public static int IndexOf(this Span span, ref T value) { ref T r0 = ref MemoryMarshal.GetReference(span); IntPtr byteOffset = Unsafe.ByteOffset(ref r0, ref value); - if (sizeof(IntPtr) == sizeof(long)) - { - long elementOffset = (long)byteOffset / Unsafe.SizeOf(); - - if ((ulong)elementOffset >= (ulong)span.Length) - { - ThrowArgumentOutOfRangeExceptionForInvalidReference(); - } + nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf(); - return unchecked((int)elementOffset); - } - else + if ((nuint)elementOffset >= (uint)span.Length) { - int elementOffset = (int)byteOffset / Unsafe.SizeOf(); - - if ((uint)elementOffset >= (uint)span.Length) - { - ThrowArgumentOutOfRangeExceptionForInvalidReference(); - } - - return elementOffset; + ThrowArgumentOutOfRangeExceptionForInvalidReference(); } + + return (int)elementOffset; } /// diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 652991e592f..759d8ea72f5 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -830,6 +830,13 @@ public bool TryGetMemory(out ReadOnlyMemory memory) } else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string)) { + // Here we need to create a Memory from the wrapped string, and to do so we need to do an inverse + // lookup to find the initial index of the string with respect to the byte offset we're currently using, + // which refers to the raw string object data. This can include variable padding or other additional + // fields on different runtimes. The lookup operation is still O(1) and just computes the byte offset + // difference between the start of the Span (which directly wraps just the actual character data + // within the string), and the input reference, which we can get from the byte offset in use. The result + // is the character index which we can use to create the final Memory instance. string text = Unsafe.As(this.instance); int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt(this.offset)); ReadOnlyMemory temp = text.AsMemory(index, (int)Length); From 0e8a2db02bde872c42f38f4d2e8fa259aca6bc74 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 29 Oct 2020 22:22:07 +0100 Subject: [PATCH 175/200] Added comments to DangerousGetReference tests --- .../Extensions/Test_ArrayExtensions.1D.cs | 6 ++++++ .../Extensions/Test_ArrayExtensions.2D.cs | 1 + .../Extensions/Test_ArrayExtensions.3D.cs | 1 + 3 files changed, 8 insertions(+) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.1D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.1D.cs index 3faa4649c56..7b12703be13 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.1D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.1D.cs @@ -17,6 +17,12 @@ public void Test_ArrayExtensions_DangerousGetReference() { string[] tokens = "aa,bb,cc,dd,ee,ff,gg,hh,ii".Split(','); + // In all these "DangerousGetReference" tests, we need to ensure that a reference to a given + // item within an array is effectively the one corresponding to the one whe expect, which is + // either a reference to the first item if we use "DangerousGetReference", or one to the n-th + // item if we use "DangerousGetReferenceAt". So all these tests just invoke the API and then + // compare the returned reference against an existing baseline (like the built-in array indexer) + // to ensure that the two are the same. These are all managed references, so no need for pinning. ref string r0 = ref Unsafe.AsRef(tokens.DangerousGetReference()); ref string r1 = ref Unsafe.AsRef(tokens[0]); diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs index d1e098c1052..6f2bdfc28e2 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs @@ -24,6 +24,7 @@ public void Test_ArrayExtensions_2D_DangerousGetReference_Int() { 9, 10, 11, 12 } }; + // See comments in Test_ArrayExtensions.1D for how these tests work ref int r0 = ref array.DangerousGetReference(); ref int r1 = ref array[0, 0]; diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.3D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.3D.cs index 9cf5ce03c28..4e7b1d24ce3 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.3D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.3D.cs @@ -16,6 +16,7 @@ public void Test_ArrayExtensions_3D_DangerousGetReference_Int() { int[,,] array = new int[10, 20, 12]; + // See comments in Test_ArrayExtensions.1D for how these tests work ref int r0 = ref array.DangerousGetReference(); ref int r1 = ref array[0, 0, 0]; From a2ecb5ff854bc43ce5c6469aae29d93ff82f72a4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 30 Oct 2020 18:16:46 +0100 Subject: [PATCH 176/200] Bug fixes, added RefEnumerable.[Try]CopyFrom --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 4 +- .../Enumerables/RefEnumerable{T}.cs | 68 ++++++++++++++++++- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index e02b4007ba1..e4390160a54 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -129,7 +129,7 @@ public readonly ref readonly T Current /// /// The destination instance. /// - /// Thrown when is shorter than the source instance. + /// Thrown when is shorter than the source instance. /// public readonly void CopyTo(Span destination) { @@ -176,7 +176,7 @@ public readonly bool TryCopyTo(Span destination) int length = this.length; #endif - if (destination.Length >= length / this.step) + if (destination.Length >= length) { CopyTo(destination); diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index a78172ef193..6418e19df8a 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -166,7 +166,7 @@ public readonly void Clear() /// /// The destination instance. /// - /// Thrown when is shorter than the source instance. + /// Thrown when is shorter than the source instance. /// public readonly void CopyTo(Span destination) { @@ -213,7 +213,7 @@ public readonly bool TryCopyTo(Span destination) int length = this.length; #endif - if (destination.Length >= length / this.step) + if (destination.Length >= length) { CopyTo(destination); @@ -223,6 +223,70 @@ public readonly bool TryCopyTo(Span destination) return false; } + /// + /// Copies the contents of a source into the current instance. + /// + /// The source instance. + /// + /// Thrown when the current is shorter than the source instance. + /// + internal readonly void CopyFrom(ReadOnlySpan source) + { +#if SPAN_RUNTIME_SUPPORT + if (this.step == 1) + { + source.CopyTo(this.span); + + return; + } + + ref T destinationRef = ref this.span.DangerousGetReference(); + int destinationLength = this.span.Length; +#else + ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); + int destinationLength = this.length; +#endif + ref T sourceRef = ref source.DangerousGetReference(); + int sourceLength = source.Length; + + if ((uint)destinationLength < (uint)sourceLength) + { + ThrowArgumentExceptionForDestinationTooShort(); + } + + nint + step = (nint)(uint)this.step, + offset = 0; + + for (int i = 0; i < sourceLength; i++, offset += step) + { + Unsafe.Add(ref destinationRef, i) = Unsafe.Add(ref sourceRef, offset); + } + } + + /// + /// Attempts to copy the source into the current instance. + /// + /// The source instance. + /// Whether or not the operation was successful. + public readonly bool TryCopyFrom(ReadOnlySpan source) + { +#if SPAN_RUNTIME_SUPPORT + int length = this.span.Length; +#else + int length = this.length; +#endif + + if (length >= source.Length) + { + CopyFrom(source); + + return true; + } + + return false; + } + /// /// Fills the elements of this with a specified value. /// From 60e17b414c6ae9932675232beef02c0cd23fd505 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 30 Oct 2020 18:24:01 +0100 Subject: [PATCH 177/200] Added [ReadOnly]Span.[Try]CopyTo RefEnumerable APIs --- .../Extensions/ReadOnlySpanExtensions.cs | 28 +++++++++++++++++++ .../Extensions/SpanExtensions.cs | 28 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs index 7f7e83a07fc..761958d18ff 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs @@ -380,5 +380,33 @@ public static int GetDjb2HashCode(this ReadOnlySpan span) 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); + } } } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs index 7121c5f747e..c05e2dace3d 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs @@ -271,6 +271,34 @@ public static int GetDjb2HashCode(this Span span) 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 Span 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 Span span, RefEnumerable destination) + { + return destination.TryCopyFrom(span); + } + /// /// Throws an when the given reference is out of range. /// From 5497eb8db38700896df50f12ad9f89a034afe33f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 30 Oct 2020 20:18:08 +0100 Subject: [PATCH 178/200] Added internal RefEnumerableHelper type --- .../Helpers/Internals/RefEnumerableHelper.cs | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs new file mode 100644 index 00000000000..3381f529645 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs @@ -0,0 +1,214 @@ +// 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.Runtime.CompilerServices; + +namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals +{ + /// + /// Helpers to process sequences of values by reference with a given step. + /// + internal static class RefEnumerableHelper + { + /// + /// Clears a target memory area. + /// + /// The type of values to clear. + /// A reference to the start of the memory area. + /// The number of items in the memory area. + /// The number of items between each consecutive target value. + public static void Clear(ref T r0, nint length, nint step) + { + nint offset = 0; + + // Main loop with 8 unrolled iterations + while (length >= 8) + { + Unsafe.Add(ref r0, offset) = default!; + Unsafe.Add(ref r0, offset += step) = default!; + Unsafe.Add(ref r0, offset += step) = default!; + Unsafe.Add(ref r0, offset += step) = default!; + Unsafe.Add(ref r0, offset += step) = default!; + Unsafe.Add(ref r0, offset += step) = default!; + Unsafe.Add(ref r0, offset += step) = default!; + Unsafe.Add(ref r0, offset += step) = default!; + + length -= 8; + offset += step; + } + + if (length >= 4) + { + Unsafe.Add(ref r0, offset) = default!; + Unsafe.Add(ref r0, offset += step) = default!; + Unsafe.Add(ref r0, offset += step) = default!; + Unsafe.Add(ref r0, offset += step) = default!; + + length -= 4; + offset += step; + } + + // Clear the remaining values + while (length > 0) + { + Unsafe.Add(ref r0, offset) = default!; + + length -= 1; + offset += step; + } + } + + /// + /// Copies a sequence of discontiguous items from one memory area to another. + /// + /// The type of items to copy. + /// The source reference to copy from. + /// The target reference to copy to. + /// The total number of items to copy. + /// The step between consecutive items in the memory area pointed to by . + public static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, nint step) + { + nint + sourceOffset = 0, + destinationOffset = 0; + + while (length >= 8) + { + Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += step); + Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += step); + Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += step); + Unsafe.Add(ref destinationRef, destinationOffset + 4) = Unsafe.Add(ref sourceRef, sourceOffset += step); + Unsafe.Add(ref destinationRef, destinationOffset + 5) = Unsafe.Add(ref sourceRef, sourceOffset += step); + Unsafe.Add(ref destinationRef, destinationOffset + 6) = Unsafe.Add(ref sourceRef, sourceOffset += step); + Unsafe.Add(ref destinationRef, destinationOffset + 7) = Unsafe.Add(ref sourceRef, sourceOffset += step); + + length -= 8; + sourceOffset += step; + destinationOffset += 8; + } + + if (length >= 4) + { + Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += step); + Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += step); + Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += step); + + length -= 4; + sourceOffset += step; + destinationOffset += 4; + } + + while (length > 0) + { + Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); + + length -= 1; + sourceOffset += step; + destinationOffset += 1; + } + } + + /// + /// Copies a sequence of discontiguous items from one memory area to another. This mirrors + /// , but refers to instead. + /// + /// The type of items to copy. + /// The source reference to copy from. + /// The target reference to copy to. + /// The total number of items to copy. + /// The step between consecutive items in the memory area pointed to by . + public static void CopyFrom(ref T sourceRef, ref T destinationRef, nint length, nint step) + { + nint + sourceOffset = 0, + destinationOffset = 0; + + while (length >= 8) + { + Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 1); + Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 2); + Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 3); + Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 4); + Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 5); + Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 6); + Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 7); + + length -= 8; + sourceOffset += 8; + destinationOffset += step; + } + + if (length >= 4) + { + Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 1); + Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 2); + Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 3); + + length -= 4; + sourceOffset += 4; + destinationOffset += step; + } + + while (length > 0) + { + Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); + + length -= 1; + sourceOffset += 1; + destinationOffset += step; + } + } + + /// + /// Fills a target memory area. + /// + /// The type of values to fill. + /// A reference to the start of the memory area. + /// The number of items in the memory area. + /// The number of items between each consecutive target value. + /// The value to assign to every item in the target memory area. + public static void Fill(ref T r0, nint length, nint step, T value) + { + nint offset = 0; + + while (length >= 8) + { + Unsafe.Add(ref r0, offset) = value; + Unsafe.Add(ref r0, offset += step) = value; + Unsafe.Add(ref r0, offset += step) = value; + Unsafe.Add(ref r0, offset += step) = value; + Unsafe.Add(ref r0, offset += step) = value; + Unsafe.Add(ref r0, offset += step) = value; + Unsafe.Add(ref r0, offset += step) = value; + Unsafe.Add(ref r0, offset += step) = value; + + length -= 8; + offset += step; + } + + if (length >= 4) + { + Unsafe.Add(ref r0, offset) = value; + Unsafe.Add(ref r0, offset += step) = value; + Unsafe.Add(ref r0, offset += step) = value; + Unsafe.Add(ref r0, offset += step) = value; + + length -= 4; + offset += step; + } + + while (length > 0) + { + Unsafe.Add(ref r0, offset) = value; + + length -= 1; + offset += step; + } + } + } +} From e54a545763a69800b618e79237246a6ccbc1fd33 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 30 Oct 2020 20:23:02 +0100 Subject: [PATCH 179/200] Code refactoring using the new helper --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 9 ++--- .../Enumerables/RefEnumerable{T}.cs | 35 +++---------------- 2 files changed, 7 insertions(+), 37 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index e4390160a54..79158f91ed6 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -10,6 +10,7 @@ using System.Runtime.InteropServices; #endif using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance.Helpers.Internals; #if !SPAN_RUNTIME_SUPPORT using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; #endif @@ -153,14 +154,8 @@ public readonly void CopyTo(Span destination) } ref T destinationRef = ref destination.DangerousGetReference(); - nint - step = (nint)(uint)this.step, - offset = 0; - for (int i = 0; i < length; i++, offset += step) - { - Unsafe.Add(ref destinationRef, i) = Unsafe.Add(ref sourceRef, offset); - } + RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)this.step); } /// diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 6418e19df8a..92b5dce57ee 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -10,6 +10,7 @@ using System.Runtime.InteropServices; #endif using Microsoft.Toolkit.HighPerformance.Extensions; +using Microsoft.Toolkit.HighPerformance.Helpers.Internals; #if !SPAN_RUNTIME_SUPPORT using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; #endif @@ -151,14 +152,8 @@ public readonly void Clear() ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); int length = this.length; #endif - nint - step = (nint)(uint)this.step, - offset = 0; - for (int i = length; i > 0; i--, offset += step) - { - Unsafe.Add(ref r0, offset) = default!; - } + RefEnumerableHelper.Clear(ref r0, (nint)(uint)length, (nint)(uint)this.step); } /// @@ -190,14 +185,8 @@ public readonly void CopyTo(Span destination) } ref T destinationRef = ref destination.DangerousGetReference(); - nint - step = (nint)(uint)this.step, - offset = 0; - for (int i = 0; i < length; i++, offset += step) - { - Unsafe.Add(ref destinationRef, i) = Unsafe.Add(ref sourceRef, offset); - } + RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)this.step); } /// @@ -254,14 +243,7 @@ internal readonly void CopyFrom(ReadOnlySpan source) ThrowArgumentExceptionForDestinationTooShort(); } - nint - step = (nint)(uint)this.step, - offset = 0; - - for (int i = 0; i < sourceLength; i++, offset += step) - { - Unsafe.Add(ref destinationRef, i) = Unsafe.Add(ref sourceRef, offset); - } + RefEnumerableHelper.CopyFrom(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.step); } /// @@ -312,14 +294,7 @@ public readonly void Fill(T value) int length = this.length; #endif - nint - step = (nint)(uint)this.step, - offset = 0; - - for (int i = length; i > 0; i--, offset += step) - { - Unsafe.Add(ref r0, offset) = value; - } + RefEnumerableHelper.Fill(ref r0, (nint)(uint)length, (nint)(uint)this.step, value); } /// From 7ab524d760072d225fbb768635cf4cb30ab3e031 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 30 Oct 2020 22:45:15 +0100 Subject: [PATCH 180/200] Added RefEnumerableHelper.CopyTo 2-step overload --- .../Helpers/Internals/RefEnumerableHelper.cs | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs index 3381f529645..4c3a96a741a 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs @@ -111,9 +111,62 @@ public static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, } } + /// + /// Copies a sequence of discontiguous items from one memory area to another. + /// + /// The type of items to copy. + /// The source reference to copy from. + /// The target reference to copy to. + /// The total number of items to copy. + /// The step between consecutive items in the memory area pointed to by . + /// The step between consecutive items in the memory area pointed to by . + public static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep, nint destinationStep) + { + nint + sourceOffset = 0, + destinationOffset = 0; + + while (length >= 8) + { + Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + + length -= 8; + sourceOffset += sourceStep; + destinationOffset += destinationStep; + } + + if (length >= 4) + { + Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + + length -= 4; + sourceOffset += sourceStep; + destinationOffset += destinationStep; + } + + while (length > 0) + { + Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); + + length -= 1; + sourceOffset += sourceStep; + destinationOffset += destinationStep; + } + } + /// /// Copies a sequence of discontiguous items from one memory area to another. This mirrors - /// , but refers to instead. + /// , but refers to instead. /// /// The type of items to copy. /// The source reference to copy from. From ffc95e6ff69f34bde4cf34b1d9fb7c74df7062c9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 30 Oct 2020 23:08:27 +0100 Subject: [PATCH 181/200] Minor code tweaks --- .../Enumerables/RefEnumerable{T}.cs | 104 +++++++++--------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 92b5dce57ee..f0deacc604a 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -29,29 +29,29 @@ public ref struct RefEnumerable /// The instance pointing to the first item in the target memory area. /// /// The field maps to the total available length. - private readonly Span span; + internal readonly Span Span; #else /// /// The target instance, if present. /// - private readonly object? instance; + internal readonly object? Instance; /// - /// The initial offset within . + /// The initial offset within . /// - private readonly IntPtr offset; + internal readonly IntPtr Offset; /// /// The total available length for the sequence. /// - private readonly int length; + internal readonly int Length; #endif /// /// The distance between items in the sequence to enumerate. /// /// The distance refers to items, not byte offset. - private readonly int step; + internal readonly int Step; /// /// The current position in the sequence. @@ -68,8 +68,9 @@ public ref struct RefEnumerable [MethodImpl(MethodImplOptions.AggressiveInlining)] internal RefEnumerable(ref T reference, int length, int step) { - this.span = MemoryMarshal.CreateSpan(ref reference, length); - this.step = step; + Span = MemoryMarshal.CreateSpan(ref reference, length); + Step = step; + this.position = -1; } #else @@ -83,10 +84,11 @@ internal RefEnumerable(ref T reference, int length, int step) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal RefEnumerable(object? instance, IntPtr offset, int length, int step) { - this.instance = instance; - this.offset = offset; - this.length = length; - this.step = step; + Instance = instance; + Offset = offset; + Length = length; + Step = step; + this.position = -1; } #endif @@ -101,9 +103,9 @@ internal RefEnumerable(object? instance, IntPtr offset, int length, int step) public bool MoveNext() { #if SPAN_RUNTIME_SUPPORT - return ++this.position < this.span.Length; + return ++this.position < this.Span.Length; #else - return ++this.position < this.length; + return ++this.position < this.Length; #endif } @@ -114,9 +116,9 @@ public readonly ref T Current get { #if SPAN_RUNTIME_SUPPORT - ref T r0 = ref this.span.DangerousGetReference(); + ref T r0 = ref this.Span.DangerousGetReference(); #else - ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); #endif // Here we just offset by shifting down as if we were traversing a 2D array with a @@ -125,7 +127,7 @@ public readonly ref T Current // being inspected. We can perform all the indexing operations in this type as nint, // as the maximum offset is guaranteed never to exceed the maximum value, since on // 32 bit architectures it's not possible to allocate that much memory anyway. - nint offset = (nint)(uint)this.position * (nint)(uint)this.step; + nint offset = (nint)(uint)this.position * (nint)(uint)this.Step; ref T ri = ref Unsafe.Add(ref r0, offset); return ref ri; @@ -139,21 +141,21 @@ public readonly void Clear() { #if SPAN_RUNTIME_SUPPORT // Fast path for contiguous items - if (this.step == 1) + if (this.Step == 1) { - this.span.Clear(); + this.Span.Clear(); return; } - ref T r0 = ref this.span.DangerousGetReference(); - int length = this.span.Length; + ref T r0 = ref this.Span.DangerousGetReference(); + int length = this.Span.Length; #else - ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); - int length = this.length; + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); + int length = this.Length; #endif - RefEnumerableHelper.Clear(ref r0, (nint)(uint)length, (nint)(uint)this.step); + RefEnumerableHelper.Clear(ref r0, (nint)(uint)length, (nint)(uint)this.Step); } /// @@ -166,18 +168,18 @@ public readonly void Clear() public readonly void CopyTo(Span destination) { #if SPAN_RUNTIME_SUPPORT - if (this.step == 1) + if (this.Step == 1) { - this.span.CopyTo(destination); + this.Span.CopyTo(destination); return; } - ref T sourceRef = ref this.span.DangerousGetReference(); - int length = this.span.Length; + ref T sourceRef = ref this.Span.DangerousGetReference(); + int length = this.Span.Length; #else - ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); - int length = this.length; + ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); + int length = this.Length; #endif if ((uint)destination.Length < (uint)length) { @@ -186,7 +188,7 @@ public readonly void CopyTo(Span destination) ref T destinationRef = ref destination.DangerousGetReference(); - RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)this.step); + RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)this.Step); } /// @@ -197,9 +199,9 @@ public readonly void CopyTo(Span destination) public readonly bool TryCopyTo(Span destination) { #if SPAN_RUNTIME_SUPPORT - int length = this.span.Length; + int length = this.Span.Length; #else - int length = this.length; + int length = this.Length; #endif if (destination.Length >= length) @@ -222,18 +224,18 @@ public readonly bool TryCopyTo(Span destination) internal readonly void CopyFrom(ReadOnlySpan source) { #if SPAN_RUNTIME_SUPPORT - if (this.step == 1) + if (this.Step == 1) { - source.CopyTo(this.span); + source.CopyTo(this.Span); return; } - ref T destinationRef = ref this.span.DangerousGetReference(); - int destinationLength = this.span.Length; + ref T destinationRef = ref this.Span.DangerousGetReference(); + int destinationLength = this.Span.Length; #else - ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); - int destinationLength = this.length; + ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); + int destinationLength = this.Length; #endif ref T sourceRef = ref source.DangerousGetReference(); int sourceLength = source.Length; @@ -243,7 +245,7 @@ internal readonly void CopyFrom(ReadOnlySpan source) ThrowArgumentExceptionForDestinationTooShort(); } - RefEnumerableHelper.CopyFrom(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.step); + RefEnumerableHelper.CopyFrom(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.Step); } /// @@ -254,9 +256,9 @@ internal readonly void CopyFrom(ReadOnlySpan source) public readonly bool TryCopyFrom(ReadOnlySpan source) { #if SPAN_RUNTIME_SUPPORT - int length = this.span.Length; + int length = this.Span.Length; #else - int length = this.length; + int length = this.Length; #endif if (length >= source.Length) @@ -280,21 +282,21 @@ public readonly bool TryCopyFrom(ReadOnlySpan source) public readonly void Fill(T value) { #if SPAN_RUNTIME_SUPPORT - if (this.step == 1) + if (this.Step == 1) { - this.span.Fill(value); + this.Span.Fill(value); return; } - ref T r0 = ref this.span.DangerousGetReference(); - int length = this.span.Length; + ref T r0 = ref this.Span.DangerousGetReference(); + int length = this.Span.Length; #else - ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); - int length = this.length; + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); + int length = this.Length; #endif - RefEnumerableHelper.Fill(ref r0, (nint)(uint)length, (nint)(uint)this.step, value); + RefEnumerableHelper.Fill(ref r0, (nint)(uint)length, (nint)(uint)this.Step, value); } /// @@ -311,9 +313,9 @@ public readonly void Fill(T value) public readonly T[] ToArray() { #if SPAN_RUNTIME_SUPPORT - int length = this.span.Length; + int length = this.Span.Length; #else - int length = this.length; + int length = this.Length; #endif // Empty array if no data is mapped From fabeba689a938b9ff400565efce725d827a7ff54 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 30 Oct 2020 23:15:18 +0100 Subject: [PATCH 182/200] Added implicit ReadOnlyRefEnumerable operator --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 46 +++++++++++++++++++ .../Enumerables/RefEnumerable{T}.cs | 12 ++--- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index 79158f91ed6..e6bc96aa1e2 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -59,6 +59,20 @@ public ref struct ReadOnlyRefEnumerable private int position; #if SPAN_RUNTIME_SUPPORT + /// + /// Initializes a new instance of the struct. + /// + /// The instance pointing to the first item in the target memory area. + /// The distance between items in the sequence to enumerate. + /// The current position in the sequence. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ReadOnlyRefEnumerable(ReadOnlySpan span, int step, int position) + { + this.span = span; + this.step = step; + this.position = position; + } + /// /// Initializes a new instance of the struct. /// @@ -73,6 +87,24 @@ internal ReadOnlyRefEnumerable(in T reference, int length, int step) this.position = -1; } #else + /// + /// Initializes a new instance of the struct. + /// + /// The target instance. + /// The initial offset within . + /// The number of items in the sequence. + /// The distance between items in the sequence to enumerate. + /// The current position in the sequence. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int step, int position) + { + this.instance = instance; + this.offset = offset; + this.length = length; + this.step = step; + this.position = position; + } + /// /// Initializes a new instance of the struct. /// @@ -204,6 +236,20 @@ public readonly T[] ToArray() return array; } + /// + /// Implicitly converts a instance into a one. + /// + /// The input instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlyRefEnumerable(RefEnumerable enumerable) + { +#if SPAN_RUNTIME_SUPPORT + return new ReadOnlyRefEnumerable(enumerable.Span, enumerable.Step, enumerable.Position); +#else + return new ReadOnlyRefEnumerable(enumerable.Instance, enumerable.Offset, enumerable.Length, enumerable.Position); +#endif + } + /// /// Throws an when the target span is too short. /// diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index f0deacc604a..caa35354997 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -56,7 +56,7 @@ public ref struct RefEnumerable /// /// The current position in the sequence. /// - private int position; + internal int Position; #if SPAN_RUNTIME_SUPPORT /// @@ -71,7 +71,7 @@ internal RefEnumerable(ref T reference, int length, int step) Span = MemoryMarshal.CreateSpan(ref reference, length); Step = step; - this.position = -1; + this.Position = -1; } #else /// @@ -89,7 +89,7 @@ internal RefEnumerable(object? instance, IntPtr offset, int length, int step) Length = length; Step = step; - this.position = -1; + this.Position = -1; } #endif @@ -103,9 +103,9 @@ internal RefEnumerable(object? instance, IntPtr offset, int length, int step) public bool MoveNext() { #if SPAN_RUNTIME_SUPPORT - return ++this.position < this.Span.Length; + return ++this.Position < this.Span.Length; #else - return ++this.position < this.Length; + return ++this.Position < this.Length; #endif } @@ -127,7 +127,7 @@ public readonly ref T Current // being inspected. We can perform all the indexing operations in this type as nint, // as the maximum offset is guaranteed never to exceed the maximum value, since on // 32 bit architectures it's not possible to allocate that much memory anyway. - nint offset = (nint)(uint)this.position * (nint)(uint)this.Step; + nint offset = (nint)(uint)this.Position * (nint)(uint)this.Step; ref T ri = ref Unsafe.Add(ref r0, offset); return ref ri; From bce507b092a64b8d5ebab2cfd45472ece264f516 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 30 Oct 2020 23:17:18 +0100 Subject: [PATCH 183/200] Removed unnecessary EditorBrowsable attributes --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 2 -- .../Enumerables/RefEnumerable{T}.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index e6bc96aa1e2..6bf307d8345 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.ComponentModel; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; #if SPAN_RUNTIME_SUPPORT @@ -21,7 +20,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables /// A that iterates readonly items from arbitrary memory locations. /// /// The type of items to enumerate. - [EditorBrowsable(EditorBrowsableState.Never)] public ref struct ReadOnlyRefEnumerable { #if SPAN_RUNTIME_SUPPORT diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index caa35354997..b75983cd130 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.ComponentModel; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; #if SPAN_RUNTIME_SUPPORT @@ -21,7 +20,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables /// A that iterates items from arbitrary memory locations. /// /// The type of items to enumerate. - [EditorBrowsable(EditorBrowsableState.Never)] public ref struct RefEnumerable { #if SPAN_RUNTIME_SUPPORT From 1a15ee21ce755afcf1dc9dd8d3fd8d772b791c08 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 30 Oct 2020 23:53:13 +0100 Subject: [PATCH 184/200] Added RefEnumerable.CopyTo(RefEnumerable) APIs --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 72 +++++++++++++++++++ .../Enumerables/RefEnumerable{T}.cs | 72 +++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index 6bf307d8345..3e6f55f56ba 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -155,6 +155,78 @@ public readonly ref readonly T Current } } + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public readonly void CopyTo(RefEnumerable destination) + { +#if SPAN_RUNTIME_SUPPORT + if (this.step == 1) + { + destination.CopyFrom(this.span); + + return; + } + + if (destination.Step == 1) + { + CopyTo(destination.Span); + + return; + } + + ref T sourceRef = ref this.span.DangerousGetReference(); + ref T destinationRef = ref destination.Span.DangerousGetReference(); + int + sourceLength = this.span.Length, + destinationLength = destination.Span.Length; +#else + ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); + ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(destination.Instance, destination.Offset); + int + sourceLength = this.length, + destinationLength = destination.Length; +#endif + + if ((uint)destinationLength < (uint)sourceLength) + { + ThrowArgumentExceptionForDestinationTooShort(); + } + + RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.step, (nint)(uint)destination.Step); + } + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operation was successful. + public readonly bool TryCopyTo(RefEnumerable destination) + { +#if SPAN_RUNTIME_SUPPORT + int + sourceLength = this.span.Length, + destinationLength = destination.Span.Length; +#else + int + sourceLength = this.length, + destinationLength = destination.Length; +#endif + + if (destinationLength >= sourceLength) + { + CopyTo(destination); + + return true; + } + + return false; + } + /// /// Copies the contents of this into a destination instance. /// diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index b75983cd130..607a9cbe739 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -156,6 +156,78 @@ public readonly void Clear() RefEnumerableHelper.Clear(ref r0, (nint)(uint)length, (nint)(uint)this.Step); } + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public readonly void CopyTo(RefEnumerable destination) + { +#if SPAN_RUNTIME_SUPPORT + if (this.Step == 1) + { + destination.CopyFrom(this.Span); + + return; + } + + if (destination.Step == 1) + { + CopyTo(destination.Span); + + return; + } + + ref T sourceRef = ref this.Span.DangerousGetReference(); + ref T destinationRef = ref destination.Span.DangerousGetReference(); + int + sourceLength = this.Span.Length, + destinationLength = destination.Span.Length; +#else + ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); + ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(destination.Instance, destination.Offset); + int + sourceLength = this.Length, + destinationLength = destination.Length; +#endif + + if ((uint)destinationLength < (uint)sourceLength) + { + ThrowArgumentExceptionForDestinationTooShort(); + } + + RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.Step, (nint)(uint)destination.Step); + } + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operation was successful. + public readonly bool TryCopyTo(RefEnumerable destination) + { +#if SPAN_RUNTIME_SUPPORT + int + sourceLength = this.Span.Length, + destinationLength = destination.Span.Length; +#else + int + sourceLength = this.Length, + destinationLength = destination.Length; +#endif + + if (destinationLength >= sourceLength) + { + CopyTo(destination); + + return true; + } + + return false; + } + /// /// Copies the contents of this into a destination instance. /// From a90866da74e47b2dafb43d0836152e430b7cba9d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 31 Oct 2020 11:46:01 +0100 Subject: [PATCH 185/200] Fixed a build error in the unit tests --- .../Extensions/Test_ArrayExtensions.2D.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs index 6f2bdfc28e2..9bb1922367d 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -246,9 +247,11 @@ public void Test_ArrayExtensions_2D_GetRowOrColumn_Helpers() CollectionAssert.AreEqual(copy, result); - Assert.ThrowsException(() => array.GetRow(0).CopyTo(default)); + Assert.ThrowsException(() => array.GetRow(0).CopyTo(default(RefEnumerable))); + Assert.ThrowsException(() => array.GetRow(0).CopyTo(default(Span))); - Assert.ThrowsException(() => array.GetColumn(0).CopyTo(default)); + Assert.ThrowsException(() => array.GetColumn(0).CopyTo(default(RefEnumerable))); + Assert.ThrowsException(() => array.GetColumn(0).CopyTo(default(Span))); Assert.IsTrue(array.GetRow(2).TryCopyTo(copy)); @@ -311,9 +314,11 @@ public void Test_ArrayExtensions_2D_ReadOnlyGetRowOrColumn_Helpers() CollectionAssert.AreEqual(copy, result); - Assert.ThrowsException(() => ((ReadOnlySpan2D)array).GetRow(0).CopyTo(default)); + Assert.ThrowsException(() => ((ReadOnlySpan2D)array).GetRow(0).CopyTo(default(RefEnumerable))); + Assert.ThrowsException(() => ((ReadOnlySpan2D)array).GetRow(0).CopyTo(default(Span))); - Assert.ThrowsException(() => ((ReadOnlySpan2D)array).GetColumn(0).CopyTo(default)); + Assert.ThrowsException(() => ((ReadOnlySpan2D)array).GetColumn(0).CopyTo(default(RefEnumerable))); + Assert.ThrowsException(() => ((ReadOnlySpan2D)array).GetColumn(0).CopyTo(default(Span))); Assert.IsTrue(span2D.GetRow(2).TryCopyTo(copy)); From 296e916f36e8a7063155b4ab5856843e1beca6d8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 31 Oct 2020 13:17:45 +0100 Subject: [PATCH 186/200] Tweaked some using directives --- .../Extensions/ReadOnlySpanExtensions.cs | 2 ++ Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs index 761958d18ff..b4264395133 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs @@ -8,7 +8,9 @@ using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; +#if SPAN_RUNTIME_SUPPORT using Microsoft.Toolkit.HighPerformance.Memory; +#endif namespace Microsoft.Toolkit.HighPerformance.Extensions { diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs index c05e2dace3d..eecf0519360 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs @@ -8,7 +8,9 @@ using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; +#if SPAN_RUNTIME_SUPPORT using Microsoft.Toolkit.HighPerformance.Memory; +#endif namespace Microsoft.Toolkit.HighPerformance.Extensions { From 14f40e283bbbb909b7ac30c47ee1e9f9d1d11641 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 31 Oct 2020 13:53:10 +0100 Subject: [PATCH 187/200] Improved unit test coverage for RefEnumerable --- .../Extensions/Test_ArrayExtensions.2D.cs | 61 ++++++++++++++ .../Extensions/Test_ReadOnlySpanExtensions.cs | 30 +++++++ .../Extensions/Test_SpanExtensions.cs | 30 +++++++ .../Memory/Test_ReadOnlySpan2D{T}.cs | 81 +++++++++++++++++++ 4 files changed, 202 insertions(+) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs index 9bb1922367d..5d11cd1ee6f 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs @@ -181,6 +181,9 @@ public void Test_ArrayExtensions_2D_GetRow_Rectangle() CollectionAssert.AreEqual(array.GetRow(1).ToArray(), new[] { 5, 6, 7, 8 }); + // Test an empty array + Assert.AreSame(new int[1, 0].GetRow(0).ToArray(), Array.Empty()); + Assert.ThrowsException(() => array.GetRow(-1)); Assert.ThrowsException(() => array.GetRow(20)); @@ -254,6 +257,7 @@ public void Test_ArrayExtensions_2D_GetRowOrColumn_Helpers() Assert.ThrowsException(() => array.GetColumn(0).CopyTo(default(Span))); Assert.IsTrue(array.GetRow(2).TryCopyTo(copy)); + Assert.IsFalse(array.GetRow(0).TryCopyTo(default(Span))); result = new[] { 9, 10, 42, 12 }; @@ -321,6 +325,7 @@ public void Test_ArrayExtensions_2D_ReadOnlyGetRowOrColumn_Helpers() Assert.ThrowsException(() => ((ReadOnlySpan2D)array).GetColumn(0).CopyTo(default(Span))); Assert.IsTrue(span2D.GetRow(2).TryCopyTo(copy)); + Assert.IsFalse(span2D.GetRow(2).TryCopyTo(default(Span))); result = new[] { 9, 10, 11, 12 }; @@ -353,6 +358,62 @@ public void Test_ArrayExtensions_2D_GetColumn_Rectangle() Assert.ThrowsException(() => array.GetColumn(20)); } + [TestCategory("ArrayExtensions")] + [TestMethod] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] + [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] + public void Test_ArrayExtensions_2D_RefEnumerable_Misc() + { + int[,] array1 = + { + { 1, 2, 3, 4 }, + { 5, 6, 7, 8 }, + { 9, 10, 11, 12 }, + { 13, 14, 15, 16 } + }; + + int[,] array2 = new int[4, 4]; + + // Copy to enumerable with source step == 1, destination step == 1 + array1.GetRow(0).CopyTo(array2.GetRow(0)); + + // Copy enumerable with source step == 1, destination step != 1 + array1.GetRow(1).CopyTo(array2.GetColumn(1)); + + // Copy enumerable with source step != 1, destination step == 1 + array1.GetColumn(2).CopyTo(array2.GetRow(2)); + + // Copy enumerable with source step != 1, destination step != 1 + array1.GetColumn(3).CopyTo(array2.GetColumn(3)); + + int[,] result = + { + { 1, 5, 3, 4 }, + { 0, 6, 0, 8 }, + { 3, 7, 11, 12 }, + { 0, 8, 0, 16 } + }; + + CollectionAssert.AreEqual(array2, result); + + // Test a valid and an invalid TryCopyTo call with the RefEnumerable overload + bool shouldBeTrue = array1.GetRow(0).TryCopyTo(array2.GetColumn(0)); + bool shouldBeFalse = array1.GetRow(0).TryCopyTo(default(RefEnumerable)); + + result = new[,] + { + { 1, 5, 3, 4 }, + { 2, 6, 0, 8 }, + { 3, 7, 11, 12 }, + { 4, 8, 0, 16 } + }; + + CollectionAssert.AreEqual(array2, result); + + Assert.IsTrue(shouldBeTrue); + Assert.IsFalse(shouldBeFalse); + } + [TestCategory("ArrayExtensions")] [TestMethod] public void Test_ArrayExtensions_2D_GetColumn_Empty() diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs index 80b6b4dfcf8..8035f16cf5f 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs @@ -267,5 +267,35 @@ public void Test_ReadOnlySpanExtensions_Tokenize_CommaWithMissingValues() CollectionAssert.AreEqual(result, tokens); } + + [TestCategory("ReadOnlySpanExtensions")] + [TestMethod] + public void Test_ReadOnlySpanExtensions_CopyTo_RefEnumerable() + { + int[,] array1 = new int[4, 5]; + + ReadOnlySpan values = new[] { 10, 20, 30, 40, 50 }; + + // Copy a span to a target row and column with valid lengths + values.CopyTo(array1.GetRow(0)); + values.Slice(0, 4).CopyTo(array1.GetColumn(1)); + + // Try to copy to a valid row and an invalid column (too short for the source span) + bool shouldBeTrue = values.TryCopyTo(array1.GetRow(2)); + bool shouldBeFalse = values.TryCopyTo(array1.GetColumn(1)); + + Assert.IsTrue(shouldBeTrue); + Assert.IsFalse(shouldBeFalse); + + int[,] result = + { + { 10, 10, 30, 40, 50 }, + { 0, 20, 0, 0, 0 }, + { 10, 20, 30, 40, 50 }, + { 0, 40, 0, 0, 0 } + }; + + CollectionAssert.AreEqual(array1, result); + } } } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_SpanExtensions.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_SpanExtensions.cs index fd89a5c6794..05c001f6a5f 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_SpanExtensions.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_SpanExtensions.cs @@ -168,5 +168,35 @@ public void Test_SpanExtensions_Enumerate_Empty() Assert.Fail("Empty source sequence"); } } + + [TestCategory("SpanExtensions")] + [TestMethod] + public void Test_SpanExtensions_CopyTo_RefEnumerable() + { + int[,] array1 = new int[4, 5]; + + int[] values = { 10, 20, 30, 40, 50 }; + + // Copy a span to a target row and column with valid lengths + values.AsSpan().CopyTo(array1.GetRow(0)); + values.AsSpan(0, 4).CopyTo(array1.GetColumn(1)); + + // Try to copy to a valid row and an invalid column (too short for the source span) + bool shouldBeTrue = values.AsSpan().TryCopyTo(array1.GetRow(2)); + bool shouldBeFalse = values.AsSpan().TryCopyTo(array1.GetColumn(1)); + + Assert.IsTrue(shouldBeTrue); + Assert.IsFalse(shouldBeFalse); + + int[,] result = + { + { 10, 10, 30, 40, 50 }, + { 0, 20, 0, 0, 0 }, + { 10, 20, 30, 40, 50 }, + { 0, 40, 0, 0, 0 } + }; + + CollectionAssert.AreEqual(array1, result); + } } } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs index 3a5d445ec01..5598bf782cb 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Enumerables; +using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.Toolkit.HighPerformance.Memory; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -692,6 +693,8 @@ public void Test_ReadOnlySpan2DT_GetRow() CollectionAssert.AreEqual(enumerable.ToArray(), expected); + Assert.AreSame(default(ReadOnlyRefEnumerable).ToArray(), Array.Empty()); + Assert.ThrowsException(() => new ReadOnlySpan2D(array).GetRow(-1)); Assert.ThrowsException(() => new ReadOnlySpan2D(array).GetRow(2)); Assert.ThrowsException(() => new ReadOnlySpan2D(array).GetRow(1000)); @@ -832,5 +835,83 @@ public void Test_ReadOnlySpan2DT_GetEnumerator_Empty() Assert.IsFalse(enumerator.MoveNext()); } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] + [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] + public void Test_ReadOnlySpan2DT_ReadOnlyRefEnumerable_Misc() + { + int[,] array1 = + { + { 1, 2, 3, 4 }, + { 5, 6, 7, 8 }, + { 9, 10, 11, 12 }, + { 13, 14, 15, 16 } + }; + + ReadOnlySpan2D span1 = array1; + + int[,] array2 = new int[4, 4]; + + // Copy to enumerable with source step == 1, destination step == 1 + span1.GetRow(0).CopyTo(array2.GetRow(0)); + + // Copy enumerable with source step == 1, destination step != 1 + span1.GetRow(1).CopyTo(array2.GetColumn(1)); + + // Copy enumerable with source step != 1, destination step == 1 + span1.GetColumn(2).CopyTo(array2.GetRow(2)); + + // Copy enumerable with source step != 1, destination step != 1 + span1.GetColumn(3).CopyTo(array2.GetColumn(3)); + + int[,] result = + { + { 1, 5, 3, 4 }, + { 0, 6, 0, 8 }, + { 3, 7, 11, 12 }, + { 0, 8, 0, 16 } + }; + + CollectionAssert.AreEqual(array2, result); + + // Test a valid and an invalid TryCopyTo call with the RefEnumerable overload + bool shouldBeTrue = span1.GetRow(0).TryCopyTo(array2.GetColumn(0)); + bool shouldBeFalse = span1.GetRow(0).TryCopyTo(default(RefEnumerable)); + + result = new[,] + { + { 1, 5, 3, 4 }, + { 2, 6, 0, 8 }, + { 3, 7, 11, 12 }, + { 4, 8, 0, 16 } + }; + + CollectionAssert.AreEqual(array2, result); + + Assert.IsTrue(shouldBeTrue); + Assert.IsFalse(shouldBeFalse); + } + + [TestCategory("ReadOnlySpan2DT")] + [TestMethod] + public void Test_ReadOnlySpan2DT_ReadOnlyRefEnumerable_Cast() + { + int[,] array1 = + { + { 1, 2, 3, 4 }, + { 5, 6, 7, 8 }, + { 9, 10, 11, 12 }, + { 13, 14, 15, 16 } + }; + + int[] result = { 5, 6, 7, 8 }; + + // Cast a RefEnumerable to a readonly one and verify the contents + int[] row = ((ReadOnlyRefEnumerable)array1.GetRow(1)).ToArray(); + + CollectionAssert.AreEqual(result, row); + } } } \ No newline at end of file From 2626c7d0942155a4dc0ee9b8559c4135181ff4af Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 31 Oct 2020 17:13:30 +0100 Subject: [PATCH 188/200] Removed unnecessary covariance array checks --- .../Memory/ReadOnlyMemory2D{T}.cs | 43 ------------------- .../Memory/ReadOnlySpan2D{T}.cs | 43 ------------------- 2 files changed, 86 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 759d8ea72f5..4b8b6b4b352 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -125,9 +125,6 @@ public ReadOnlyMemory2D(string text, int offset, int height, int width, int pitc /// The target array to wrap. /// The height of the resulting 2D area. /// The width of each row in the resulting 2D area. - /// - /// Thrown when doesn't match . - /// /// /// Thrown when either or are invalid. /// @@ -145,9 +142,6 @@ public ReadOnlyMemory2D(T[] array, int height, int width) /// The height of the resulting 2D area. /// The width of each row in the resulting 2D area. /// The pitch in the resulting 2D area. - /// - /// Thrown when doesn't match . - /// /// /// Thrown when one of the input parameters is out of range. /// @@ -156,11 +150,6 @@ public ReadOnlyMemory2D(T[] array, int height, int width) /// public ReadOnlyMemory2D(T[] array, int offset, int height, int width, int pitch) { - if (array.IsCovariant()) - { - ThrowHelper.ThrowArrayTypeMismatchException(); - } - if ((uint)offset > (uint)array.Length) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); @@ -201,9 +190,6 @@ public ReadOnlyMemory2D(T[] array, int offset, int height, int width, int pitch) /// Initializes a new instance of the struct wrapping a 2D array. /// /// The given 2D array to wrap. - /// - /// Thrown when doesn't match . - /// public ReadOnlyMemory2D(T[,]? array) { if (array is null) @@ -213,11 +199,6 @@ public ReadOnlyMemory2D(T[,]? array) return; } - if (array.IsCovariant()) - { - ThrowHelper.ThrowArrayTypeMismatchException(); - } - this.instance = array; this.offset = GetArray2DDataByteOffset(); this.height = array.GetLength(0); @@ -233,9 +214,6 @@ public ReadOnlyMemory2D(T[,]? array) /// The target column to map within . /// The height to map within . /// The width to map within . - /// - /// Thrown when doesn't match . - /// /// /// Thrown when either , or /// are negative or not within the bounds that are valid for . @@ -254,11 +232,6 @@ public ReadOnlyMemory2D(T[,]? array, int row, int column, int height, int width) return; } - if (array.IsCovariant()) - { - ThrowHelper.ThrowArrayTypeMismatchException(); - } - int rows = array.GetLength(0), columns = array.GetLength(1); @@ -295,17 +268,9 @@ public ReadOnlyMemory2D(T[,]? array, int row, int column, int height, int width) /// /// The given 3D array to wrap. /// The target layer to map within . - /// - /// Thrown when doesn't match . - /// /// Thrown when a parameter is invalid. public ReadOnlyMemory2D(T[,,] array, int depth) { - if (array.IsCovariant()) - { - ThrowHelper.ThrowArrayTypeMismatchException(); - } - if ((uint)depth >= (uint)array.GetLength(0)) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); @@ -327,17 +292,9 @@ public ReadOnlyMemory2D(T[,,] array, int depth) /// The target column to map within . /// The height to map within . /// The width to map within . - /// - /// Thrown when doesn't match . - /// /// Thrown when a parameter is invalid. public ReadOnlyMemory2D(T[,,] array, int depth, int row, int column, int height, int width) { - if (array.IsCovariant()) - { - ThrowHelper.ThrowArrayTypeMismatchException(); - } - if ((uint)depth >= (uint)array.GetLength(0)) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 30560a42dee..9c22eaf25b8 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -143,9 +143,6 @@ internal ReadOnlySpan2D(object? instance, IntPtr offset, int height, int width, /// The target array to wrap. /// The height of the resulting 2D area. /// The width of each row in the resulting 2D area. - /// - /// Thrown when doesn't match . - /// /// /// Thrown when either or are invalid. /// @@ -163,9 +160,6 @@ public ReadOnlySpan2D(T[] array, int height, int width) /// The height of the resulting 2D area. /// The width of each row in the resulting 2D area. /// The pitch in the resulting 2D area. - /// - /// Thrown when doesn't match . - /// /// /// Thrown when one of the input parameters is out of range. /// @@ -174,11 +168,6 @@ public ReadOnlySpan2D(T[] array, int height, int width) /// public ReadOnlySpan2D(T[] array, int offset, int height, int width, int pitch) { - if (array.IsCovariant()) - { - ThrowHelper.ThrowArrayTypeMismatchException(); - } - if ((uint)offset > (uint)array.Length) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset(); @@ -230,9 +219,6 @@ public ReadOnlySpan2D(T[] array, int offset, int height, int width, int pitch) /// Initializes a new instance of the struct wrapping a 2D array. /// /// The given 2D array to wrap. - /// - /// Thrown when doesn't match . - /// public ReadOnlySpan2D(T[,]? array) { if (array is null) @@ -242,11 +228,6 @@ public ReadOnlySpan2D(T[,]? array) return; } - if (array.IsCovariant()) - { - ThrowHelper.ThrowArrayTypeMismatchException(); - } - #if SPAN_RUNTIME_SUPPORT this.span = MemoryMarshal.CreateReadOnlySpan(ref array.DangerousGetReference(), array.GetLength(0)); #else @@ -265,9 +246,6 @@ public ReadOnlySpan2D(T[,]? array) /// The target column to map within . /// The height to map within . /// The width to map within . - /// - /// Thrown when doesn't match . - /// /// /// Thrown when either , or /// are negative or not within the bounds that are valid for . @@ -286,11 +264,6 @@ public ReadOnlySpan2D(T[,]? array, int row, int column, int height, int width) return; } - if (array.IsCovariant()) - { - ThrowHelper.ThrowArrayTypeMismatchException(); - } - int rows = array.GetLength(0), columns = array.GetLength(1); @@ -331,17 +304,9 @@ public ReadOnlySpan2D(T[,]? array, int row, int column, int height, int width) /// /// The given 3D array to wrap. /// The target layer to map within . - /// - /// Thrown when doesn't match . - /// /// Thrown when a parameter is invalid. public ReadOnlySpan2D(T[,,] array, int depth) { - if (array.IsCovariant()) - { - ThrowHelper.ThrowArrayTypeMismatchException(); - } - if ((uint)depth >= (uint)array.GetLength(0)) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); @@ -366,17 +331,9 @@ public ReadOnlySpan2D(T[,,] array, int depth) /// The target column to map within . /// The height to map within . /// The width to map within . - /// - /// Thrown when doesn't match . - /// /// Thrown when a parameter is invalid. public ReadOnlySpan2D(T[,,] array, int depth, int row, int column, int height, int width) { - if (array.IsCovariant()) - { - ThrowHelper.ThrowArrayTypeMismatchException(); - } - if ((uint)depth >= (uint)array.GetLength(0)) { ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); From 8a70ace12f823a60f405a9e4f05613e267b7b132 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 31 Oct 2020 18:24:30 +0100 Subject: [PATCH 189/200] Added comments to unit tests --- .../Extensions/Test_ArrayExtensions.2D.cs | 78 ++++++++++----- .../Extensions/Test_BoolExtensions.cs | 2 + .../Test_ParallelHelper.ForEach.In2D.cs | 6 +- .../Test_ParallelHelper.ForEach.Ref2D.cs | 2 + .../Memory/Test_Memory2D{T}.cs | 49 ++++++++++ .../Memory/Test_ReadOnlyMemory2D{T}.cs | 7 ++ .../Memory/Test_ReadOnlySpan2D{T}.cs | 5 + .../Memory/Test_Span2D{T}.cs | 96 ++++++++++++++++++- 8 files changed, 216 insertions(+), 29 deletions(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs index 5d11cd1ee6f..8215eba7a12 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs @@ -88,6 +88,11 @@ public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayMid() { bool[,] test = new bool[4, 5]; + // To fill an array we now go through the Span2D type, which includes all + // the necessary logic to perform the operation. In these tests we just create + // one through the extension, slice it and then fill it. For instance in this + // one, we're creating a Span2D from coordinates (1, 1), with a height of + // 2 and a width of 2, and then filling it. Then we just compare the results. test.AsSpan2D(1, 1, 2, 3).Fill(true); var expected = new[,] @@ -173,12 +178,16 @@ public void Test_ArrayExtensions_2D_GetRow_Rectangle() { 9, 10, 11, 12 } }; + // Here we use the enumerator on the RefEnumerator type to traverse items in a row + // by reference. For each one, we check that the reference does in fact point to the + // item we expect in the underlying array (in this case, items on row 1). int j = 0; foreach (ref int value in array.GetRow(1)) { Assert.IsTrue(Unsafe.AreSame(ref value, ref array[1, j++])); } + // Check that RefEnumerable.ToArray() works correctly CollectionAssert.AreEqual(array.GetRow(1).ToArray(), new[] { 5, 6, 7, 8 }); // Test an empty array @@ -189,12 +198,40 @@ public void Test_ArrayExtensions_2D_GetRow_Rectangle() Assert.ThrowsException(() => array.GetRow(20)); } + [TestCategory("ArrayExtensions")] + [TestMethod] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] + [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] + public void Test_ArrayExtensions_2D_GetColumn_Rectangle() + { + int[,] array = + { + { 1, 2, 3, 4 }, + { 5, 6, 7, 8 }, + { 9, 10, 11, 12 } + }; + + // Same as above, but this time we iterate a column instead (so non contiguous items) + int i = 0; + foreach (ref int value in array.GetColumn(1)) + { + Assert.IsTrue(Unsafe.AreSame(ref value, ref array[i++, 1])); + } + + CollectionAssert.AreEqual(array.GetColumn(1).ToArray(), new[] { 2, 6, 10 }); + + Assert.ThrowsException(() => array.GetColumn(-1)); + + Assert.ThrowsException(() => array.GetColumn(20)); + } + [TestCategory("ArrayExtensions")] [TestMethod] public void Test_ArrayExtensions_2D_GetRow_Empty() { int[,] array = new int[0, 0]; + // Try to get a row from an empty array (the row index isn't in range) Assert.ThrowsException(() => array.GetRow(0).ToArray()); } @@ -212,6 +249,8 @@ public void Test_ArrayExtensions_2D_GetRowOrColumn_Helpers() { 13, 14, 15, 16 } }; + // Get a row and test the Clear method. Note that the Span2D here is sliced + // starting from the second column, so this method should clear the row from index 1. array.AsSpan2D(1, 1, 3, 3).GetRow(0).Clear(); int[,] expected = @@ -224,6 +263,7 @@ public void Test_ArrayExtensions_2D_GetRowOrColumn_Helpers() CollectionAssert.AreEqual(array, expected); + // Same as before, but this time we fill a column with a value array.GetColumn(2).Fill(42); expected = new[,] @@ -238,24 +278,28 @@ public void Test_ArrayExtensions_2D_GetRowOrColumn_Helpers() int[] copy = new int[4]; + // Get a row and copy items to a target span (in this case, wrapping an array) array.GetRow(2).CopyTo(copy); int[] result = { 9, 10, 42, 12 }; CollectionAssert.AreEqual(copy, result); + // Same as above, but copying from a column (so we test non contiguous sequences too) array.GetColumn(1).CopyTo(copy); result = new[] { 2, 0, 10, 14 }; CollectionAssert.AreEqual(copy, result); + // Some invalid attempts to copy to an empty span or sequence Assert.ThrowsException(() => array.GetRow(0).CopyTo(default(RefEnumerable))); Assert.ThrowsException(() => array.GetRow(0).CopyTo(default(Span))); Assert.ThrowsException(() => array.GetColumn(0).CopyTo(default(RefEnumerable))); Assert.ThrowsException(() => array.GetColumn(0).CopyTo(default(Span))); + // Same as CopyTo, but this will fail gracefully with an invalid target Assert.IsTrue(array.GetRow(2).TryCopyTo(copy)); Assert.IsFalse(array.GetRow(0).TryCopyTo(default(Span))); @@ -263,6 +307,7 @@ public void Test_ArrayExtensions_2D_GetRowOrColumn_Helpers() CollectionAssert.AreEqual(copy, result); + // Also fill a row and then further down clear a column (trying out all possible combinations) array.GetRow(2).Fill(99); expected = new[,] @@ -302,6 +347,9 @@ public void Test_ArrayExtensions_2D_ReadOnlyGetRowOrColumn_Helpers() { 13, 14, 15, 16 } }; + // This test pretty much does the same things as the method above, but this time + // using a source ReadOnlySpan2D, so that the sequence type being tested is + // ReadOnlyRefEnumerable instead (which shares most features but is separate). ReadOnlySpan2D span2D = array; int[] copy = new int[4]; @@ -332,32 +380,6 @@ public void Test_ArrayExtensions_2D_ReadOnlyGetRowOrColumn_Helpers() CollectionAssert.AreEqual(copy, result); } - [TestCategory("ArrayExtensions")] - [TestMethod] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] - [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] - public void Test_ArrayExtensions_2D_GetColumn_Rectangle() - { - int[,] array = - { - { 1, 2, 3, 4 }, - { 5, 6, 7, 8 }, - { 9, 10, 11, 12 } - }; - - int i = 0; - foreach (ref int value in array.GetColumn(1)) - { - Assert.IsTrue(Unsafe.AreSame(ref value, ref array[i++, 1])); - } - - CollectionAssert.AreEqual(array.GetColumn(1).ToArray(), new[] { 2, 6, 10 }); - - Assert.ThrowsException(() => array.GetColumn(-1)); - - Assert.ThrowsException(() => array.GetColumn(20)); - } - [TestCategory("ArrayExtensions")] [TestMethod] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] @@ -432,6 +454,7 @@ public void Test_ArrayExtensions_2D_AsSpan_Empty() Span span = array.AsSpan(); + // Check that the empty array was loaded properly Assert.AreEqual(span.Length, array.Length); Assert.IsTrue(span.IsEmpty); } @@ -449,11 +472,14 @@ public void Test_ArrayExtensions_2D_AsSpan_Populated() Span span = array.AsSpan(); + // Test the total length of the span Assert.AreEqual(span.Length, array.Length); ref int r0 = ref array[0, 0]; ref int r1 = ref span[0]; + // Similarly to the top methods, here we compare a given reference to + // ensure they point to the right element back in the original array. Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1)); } #endif diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_BoolExtensions.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_BoolExtensions.cs index f5b3db0a9f9..db03bb8e669 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_BoolExtensions.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_BoolExtensions.cs @@ -15,6 +15,8 @@ public class Test_BoolExtensions [TestMethod] public void Test_BoolExtensions_True() { + // There tests all just run a couple of boolean expressions and validate that the extension + // correctly produces either 1 or 0 depending on whether the expression was true or false. Assert.AreEqual(1, true.ToByte(), nameof(Test_BoolExtensions_True)); Assert.AreEqual(1, (DateTime.Now.Year > 0).ToByte(), nameof(Test_BoolExtensions_True)); } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In2D.cs index 2311db56e80..2fd83fe931c 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In2D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In2D.cs @@ -35,6 +35,7 @@ public unsafe void Test_ParallelHelper_ForEach_In2D( { int[,] data = CreateRandomData2D(sizeY, sizeX); + // Create a memory wrapping the random array with the given parameters ReadOnlyMemory2D memory = data.AsMemory2D(row, column, height, width); Assert.AreEqual(memory.Length, height * width); @@ -43,13 +44,16 @@ public unsafe void Test_ParallelHelper_ForEach_In2D( int sum = 0; + // Sum all the items in parallel. The Summer type takes a pointer to a target value + // and adds values to it in a thread-safe manner (with an interlocked add). ParallelHelper.ForEach(memory, new Summer(&sum)); int expected = 0; + // Calculate the sum iteratively as a baseline for comparison foreach (int n in memory.Span) { - Interlocked.Add(ref Unsafe.AsRef(&expected), n); + expected += n; } Assert.AreEqual(sum, expected, $"The sum doesn't match, was {sum} instead of {expected}"); diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref2D.cs index db4406768be..d3a27abcea8 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref2D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref2D.cs @@ -33,6 +33,7 @@ public void Test_ParallelHelper_ForEach_Ref2D( data = CreateRandomData2D(sizeY, sizeX), copy = (int[,])data.Clone(); + // Prepare the target data iteratively foreach (ref int n in copy.AsSpan2D(row, column, height, width)) { n = unchecked(n * 397); @@ -44,6 +45,7 @@ public void Test_ParallelHelper_ForEach_Ref2D( Assert.AreEqual(memory.Height, height); Assert.AreEqual(memory.Width, width); + // Do the same computation in paralellel, then compare the two arrays ParallelHelper.ForEach(memory, new Multiplier(397)); CollectionAssert.AreEqual(data, copy); diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs index 3146e01a459..55652fd4644 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs @@ -21,6 +21,8 @@ public class Test_Memory2DT [TestMethod] public void Test_Memory2DT_Empty() { + // Create a few empty Memory2D instances in different ways and + // check to ensure the right parameters were used to initialize them. Memory2D empty1 = default; Assert.IsTrue(empty1.IsEmpty); @@ -59,6 +61,8 @@ public void Test_Memory2DT_Array1DConstructor() 1, 2, 3, 4, 5, 6 }; + // Create a memory over a 1D array with 2D data in row-major order. This tests + // the T[] array constructor for Memory2D with custom size and pitch. Memory2D memory2d = new Memory2D(array, 1, 2, 2, 1); Assert.IsFalse(memory2d.IsEmpty); @@ -68,6 +72,8 @@ public void Test_Memory2DT_Array1DConstructor() Assert.AreEqual(memory2d.Span[0, 0], 2); Assert.AreEqual(memory2d.Span[1, 1], 6); + // Also ensure the right exceptions are thrown with invalid parameters, such as + // negative indices, indices out of range, values that are too big, etc. Assert.ThrowsException(() => new Memory2D(new string[1], 1, 1)); Assert.ThrowsException(() => new Memory2D(array, -99, 1, 1, 1)); Assert.ThrowsException(() => new Memory2D(array, 0, -10, 1, 1)); @@ -86,6 +92,7 @@ public void Test_Memory2DT_Array2DConstructor_1() { 4, 5, 6 } }; + // Test the constructor taking a T[,] array that is mapped directly (no slicing) Memory2D memory2d = new Memory2D(array); Assert.IsFalse(memory2d.IsEmpty); @@ -95,6 +102,8 @@ public void Test_Memory2DT_Array2DConstructor_1() Assert.AreEqual(memory2d.Span[0, 1], 2); Assert.AreEqual(memory2d.Span[1, 2], 6); + // Here we test the check for covariance: we can't create a Memory2D from a U[,] array + // where U is assignable to T (as in, U : T). This would cause a type safety violation on write. Assert.ThrowsException(() => new Memory2D(new string[1, 2])); } @@ -108,6 +117,7 @@ public void Test_Memory2DT_Array2DConstructor_2() { 4, 5, 6 } }; + // Same as above, but this time we also slice the memory to test the other constructor Memory2D memory2d = new Memory2D(array, 0, 1, 2, 2); Assert.IsFalse(memory2d.IsEmpty); @@ -136,6 +146,7 @@ public void Test_Memory2DT_Array3DConstructor_1() } }; + // Same as above, but we test the constructor taking a layer within a 3D array Memory2D memory2d = new Memory2D(array, 1); Assert.IsFalse(memory2d.IsEmpty); @@ -145,6 +156,7 @@ public void Test_Memory2DT_Array3DConstructor_1() Assert.AreEqual(memory2d.Span[0, 1], 20); Assert.AreEqual(memory2d.Span[1, 2], 60); + // A couple of tests for invalid parameters, ie. layers out of range Assert.ThrowsException(() => new Memory2D(array, -1)); Assert.ThrowsException(() => new Memory2D(array, 20)); } @@ -165,6 +177,10 @@ public void Test_Memory2DT_Array3DConstructor_2() } }; + // Same as above, but we also slice the target layer in the 3D array. In this case we're creating + // a Memory instance from a slice in the layer at depth 1 in our 3D array, and with an area + // starting at coorsinates (0, 1), with a height of 2 and width of 2. So we want to wrap the + // square with items [20, 30, 50, 60] in the second layer of the 3D array above. Memory2D memory2d = new Memory2D(array, 1, 0, 1, 2, 2); Assert.IsFalse(memory2d.IsEmpty); @@ -174,6 +190,7 @@ public void Test_Memory2DT_Array3DConstructor_2() Assert.AreEqual(memory2d.Span[0, 0], 20); Assert.AreEqual(memory2d.Span[1, 1], 60); + // Same as above, testing a few cases with invalid parameters Assert.ThrowsException(() => new Memory2D(array, -1, 1, 1, 1, 1)); Assert.ThrowsException(() => new Memory2D(array, 1, -1, 1, 1, 1)); Assert.ThrowsException(() => new Memory2D(array, 1, 1, -1, 1, 1)); @@ -191,6 +208,10 @@ public void Test_Memory2DT_MemoryConstructor() 1, 2, 3, 4, 5, 6 }; + // We also test the constructor that takes an input Memory instance. + // This is only available on runtimes with fast Span support, as otherwise + // the implementation would be too complex and slow to work in this case. + // Conceptually, this works the same as when wrapping a 1D array with row-major items. Memory2D memory2d = memory.AsMemory2D(1, 2, 2, 1); Assert.IsFalse(memory2d.IsEmpty); @@ -220,6 +241,7 @@ public void Test_Memory2DT_Slice_1() Memory2D memory2d = new Memory2D(array); + // Test a slice from a Memory2D with valid parameters Memory2D slice1 = memory2d.Slice(1, 1, 1, 2); Assert.AreEqual(slice1.Length, 2); @@ -228,6 +250,9 @@ public void Test_Memory2DT_Slice_1() Assert.AreEqual(slice1.Span[0, 0], 5); Assert.AreEqual(slice1.Span[0, 1], 6); + // Same above, but we test slicing a pre-sliced instance as well. This + // is done to verify that the internal offsets are properly tracked + // across multiple slicing operations, instead of just in the first. Memory2D slice2 = memory2d.Slice(0, 1, 2, 2); Assert.AreEqual(slice2.Length, 4); @@ -237,6 +262,7 @@ public void Test_Memory2DT_Slice_1() Assert.AreEqual(slice2.Span[1, 0], 5); Assert.AreEqual(slice2.Span[1, 1], 6); + // A few invalid slicing operations, with out of range parameters Assert.ThrowsException(() => new Memory2D(array).Slice(-1, 1, 1, 1)); Assert.ThrowsException(() => new Memory2D(array).Slice(1, -1, 1, 1)); Assert.ThrowsException(() => new Memory2D(array).Slice(1, 1, 1, -1)); @@ -258,6 +284,7 @@ public void Test_Memory2DT_Slice_2() Memory2D memory2d = new Memory2D(array); + // Mostly the same test as above, just with different parameters Memory2D slice1 = memory2d.Slice(0, 0, 2, 2); Assert.AreEqual(slice1.Length, 4); @@ -294,6 +321,10 @@ public void Test_Memory2DT_TryGetMemory_1() Memory2D memory2d = new Memory2D(array); + // Here we test that we can get a Memory from a 2D one when the underlying + // data is contiguous. Note that in this case this can only work on runtimes + // with fast Span support, because otherwise it's not possible to get a + // Memory (or a Span too, for that matter) from a 2D array. bool success = memory2d.TryGetMemory(out Memory memory); #if WINDOWS_UWP @@ -314,6 +345,8 @@ public void Test_Memory2DT_TryGetMemory_2() Memory2D memory2d = new Memory2D(array, 2, 2); + // Same test as above, but this will always succeed on all runtimes, + // as creating a Memory from a 1D array is always supported. bool success = memory2d.TryGetMemory(out Memory memory); Assert.IsTrue(success); @@ -330,6 +363,9 @@ public void Test_Memory2DT_TryGetMemory_3() Memory2D memory2d = data.AsMemory2D(2, 2); + // Same as above, just with the extra Memory indirection. Same as above, + // this test is only supported on runtimes with fast Span support. + // On others, we just don't expose the Memory.AsMemory2D extension. bool success = memory2d.TryGetMemory(out Memory memory); Assert.IsTrue(success); @@ -344,6 +380,8 @@ public unsafe void Test_Memory2DT_Pin_1() { int[] array = { 1, 2, 3, 4 }; + // We create a Memory2D from an array and verify that pinning this + // instance correctly returns a pointer to the right array element. Memory2D memory2d = new Memory2D(array, 2, 2); using var pin = memory2d.Pin(); @@ -358,6 +396,7 @@ public unsafe void Test_Memory2DT_Pin_2() { int[] array = { 1, 2, 3, 4 }; + // Same as above, but we test with a sliced Memory2D instance Memory2D memory2d = new Memory2D(array, 2, 2); using var pin = memory2d.Pin(); @@ -376,6 +415,8 @@ public void Test_Memory2DT_ToArray_1() { 4, 5, 6 } }; + // Here we create a Memory2D instance from a 2D array and then verify that + // calling ToArray() creates an array that matches the contents of the first. Memory2D memory2d = new Memory2D(array); int[,] copy = memory2d.ToArray(); @@ -396,6 +437,7 @@ public void Test_Memory2DT_ToArray_2() { 4, 5, 6 } }; + // Same as above, but with a sliced Memory2D instance Memory2D memory2d = new Memory2D(array, 0, 0, 2, 2); int[,] copy = memory2d.ToArray(); @@ -422,6 +464,8 @@ public void Test_Memory2DT_Equals() { 4, 5, 6 } }; + // Here we want to verify that the Memory2D.Equals method works correctly. This is true + // when the wrapped instance is the same, and the various internal offsets and sizes match. Memory2D memory2d = new Memory2D(array); Assert.IsFalse(memory2d.Equals(null)); @@ -429,6 +473,7 @@ public void Test_Memory2DT_Equals() Assert.IsTrue(memory2d.Equals(new Memory2D(array))); Assert.IsTrue(memory2d.Equals(memory2d)); + // This should work also when casting to a ReadOnlyMemory2D instance ReadOnlyMemory2D readOnlyMemory2d = memory2d; Assert.IsTrue(memory2d.Equals(readOnlyMemory2d)); @@ -439,6 +484,7 @@ public void Test_Memory2DT_Equals() [TestMethod] public void Test_Memory2DT_GetHashCode() { + // An emoty Memory2D has just 0 as the hashcode Assert.AreEqual(Memory2D.Empty.GetHashCode(), 0); int[,] array = @@ -449,10 +495,12 @@ public void Test_Memory2DT_GetHashCode() Memory2D memory2d = new Memory2D(array); + // Ensure that the GetHashCode method is repeatable int a = memory2d.GetHashCode(), b = memory2d.GetHashCode(); Assert.AreEqual(a, b); + // The hashcode shouldn't match when the size is different int c = new Memory2D(array, 0, 1, 2, 2).GetHashCode(); Assert.AreNotEqual(a, c); @@ -470,6 +518,7 @@ public void Test_Memory2DT_ToString() Memory2D memory2d = new Memory2D(array); + // Here we just want to verify that the type is nicely printed as expected, along with the size string text = memory2d.ToString(); const string expected = "Microsoft.Toolkit.HighPerformance.Memory.Memory2D[2, 3]"; diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs index 2bf5a4303aa..e161e8f02a8 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs @@ -13,6 +13,13 @@ namespace UnitTests.HighPerformance.Memory { + /* ==================================================================== + * NOTE + * ==================================================================== + * All the tests here mirror the ones for Memory2D, as the two types + * are basically the same except for some small differences in return types + * or some checks being done upon construction. See comments in the test + * file for Memory2D for more info on these tests. */ [TestClass] [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")] public class Test_ReadOnlyMemory2DT diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs index 5598bf782cb..537de8b8f5e 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs @@ -12,6 +12,11 @@ namespace UnitTests.HighPerformance.Memory { + /* ==================================================================== + * NOTE + * ==================================================================== + * All the tests here mirror the ones for ReadOnlySpan2D. See comments + * in the test file for Span2D for more info on these tests. */ [TestClass] [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")] public class Test_ReadOnlySpan2DT diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs index a87428c5fd9..c5c495a1504 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Span2D{T}.cs @@ -20,6 +20,7 @@ public class Test_Span2DT [TestMethod] public void Test_Span2DT_Empty() { + // Like in the tests for Memory2D, here we validate a number of empty spans Span2D empty1 = default; Assert.IsTrue(empty1.IsEmpty); @@ -59,6 +60,8 @@ public unsafe void Test_Span2DT_RefConstructor() 1, 2, 3, 4, 5, 6 }; + // Test for a Span2D instance created from a target reference. This is only supported + // on runtimes with fast Span support (as we need the API to power this with just a ref). Span2D span2d = Span2D.DangerousCreate(ref span[0], 2, 3, 0); Assert.IsFalse(span2d.IsEmpty); @@ -69,9 +72,11 @@ public unsafe void Test_Span2DT_RefConstructor() span2d[0, 0] = 99; span2d[1, 2] = 101; + // Validate that those values were mapped to the right spot in the target span Assert.AreEqual(span[0], 99); Assert.AreEqual(span[5], 101); + // A few cases with invalid indices Assert.ThrowsException(() => Span2D.DangerousCreate(ref Unsafe.AsRef(null), -1, 0, 0)); Assert.ThrowsException(() => Span2D.DangerousCreate(ref Unsafe.AsRef(null), 1, -2, 0)); Assert.ThrowsException(() => Span2D.DangerousCreate(ref Unsafe.AsRef(null), 1, 0, -5)); @@ -92,6 +97,7 @@ public unsafe void Test_Span2DT_PtrConstructor() 6 }; + // Same as above, but creating a Span2D from a raw pointer Span2D span2d = new Span2D(ptr, 2, 3, 0); Assert.IsFalse(span2d.IsEmpty); @@ -120,6 +126,7 @@ public void Test_Span2DT_Array1DConstructor() 1, 2, 3, 4, 5, 6 }; + // Same as above, but wrapping a 1D array with data in row-major order Span2D span2d = new Span2D(array, 1, 2, 2, 1); Assert.IsFalse(span2d.IsEmpty); @@ -133,6 +140,8 @@ public void Test_Span2DT_Array1DConstructor() Assert.AreEqual(array[1], 99); Assert.AreEqual(array[5], 101); + // The first check fails due to the array covariance test mentioned in the Memory2D tests. + // The others just validate a number of cases with invalid arguments (eg. out of range). Assert.ThrowsException(() => new Span2D(new string[1], 1, 1)); Assert.ThrowsException(() => new Span2D(array, -99, 1, 1, 1)); Assert.ThrowsException(() => new Span2D(array, 0, -10, 1, 1)); @@ -151,6 +160,7 @@ public void Test_Span2DT_Array2DConstructor_1() { 4, 5, 6 } }; + // Same as above, but directly wrapping a 2D array Span2D span2d = new Span2D(array); Assert.IsFalse(span2d.IsEmpty); @@ -177,6 +187,7 @@ public void Test_Span2DT_Array2DConstructor_2() { 4, 5, 6 } }; + // Same as above, but with a custom slicing over the target 2D array Span2D span2d = new Span2D(array, 0, 1, 2, 2); Assert.IsFalse(span2d.IsEmpty); @@ -209,6 +220,7 @@ public void Test_Span2DT_Array3DConstructor_1() } }; + // Here we wrap a layer in a 3D array instead, the rest is the same Span2D span2d = new Span2D(array, 1); Assert.IsFalse(span2d.IsEmpty); @@ -243,6 +255,7 @@ public void Test_Span2DT_Array3DConstructor_2() } }; + // Same as above, but also slicing a target 2D area in the 3D array layer Span2D span2d = new Span2D(array, 1, 0, 1, 2, 2); Assert.IsFalse(span2d.IsEmpty); @@ -274,6 +287,8 @@ public void Test_Span2DT_FillAndClear_1() { 4, 5, 6 } }; + // Tests for the Fill and Clear APIs for Span2D. These should fill + // or clear the entire wrapped 2D array (just like eg. Span.Fill). Span2D span2d = new Span2D(array); span2d.Fill(42); @@ -295,6 +310,8 @@ public void Test_Span2DT_Fill_Empty() { 4, 5, 6 } }; + // Same as above, but with an initial slicing as well to ensure + // these method work correctly with different internal offsets Span2D span2d = new Span2D(array, 0, 0, 0, 0); span2d.Fill(42); @@ -316,6 +333,7 @@ public void Test_Span2DT_FillAndClear_2() { 4, 5, 6 } }; + // Same as above, just with different slicing to a target smaller 2D area Span2D span2d = new Span2D(array, 0, 1, 2, 2); span2d.Fill(42); @@ -347,6 +365,7 @@ public void Test_Span2DT_CopyTo_Empty() int[] target = new int[0]; + // Copying an emoty Span2D to an empty array is just a no-op span2d.CopyTo(target); } @@ -364,10 +383,13 @@ public void Test_Span2DT_CopyTo_1() int[] target = new int[array.Length]; + // Here we copy a Span2D to a target Span mapping an array. + // This is valid, and the data will just be copied in row-major order. span2d.CopyTo(target); CollectionAssert.AreEqual(array, target); + // Exception due to the target span being too small for the source Span2D instance Assert.ThrowsException(() => new Span2D(array).CopyTo(Span.Empty)); } @@ -381,6 +403,7 @@ public void Test_Span2DT_CopyTo_2() { 4, 5, 6 } }; + // Same as above, but with different initial slicing Span2D span2d = new Span2D(array, 0, 1, 2, 2); int[] target = new int[4]; @@ -409,6 +432,8 @@ public void Test_Span2DT_CopyTo2D_1() int[,] target = new int[2, 3]; + // Same as above, but copying to a target Span2D instead. Note + // that this method uses the implicit T[,] to Span2D conversion. span2d.CopyTo(target); CollectionAssert.AreEqual(array, target); @@ -426,6 +451,7 @@ public void Test_Span2DT_CopyTo2D_2() { 4, 5, 6 } }; + // Same as above, but with extra initial slicing Span2D span2d = new Span2D(array, 0, 1, 2, 2); int[,] target = new int[2, 2]; @@ -457,8 +483,13 @@ public void Test_Span2DT_TryCopyTo() int[] target = new int[array.Length]; + // Here we test the safe TryCopyTo method, which will fail gracefully Assert.IsTrue(span2d.TryCopyTo(target)); Assert.IsFalse(span2d.TryCopyTo(Span.Empty)); + + int[] expected = { 1, 2, 3, 4, 5, 6 }; + + CollectionAssert.AreEqual(target, expected); } [TestCategory("Span2DT")] @@ -471,18 +502,28 @@ public void Test_Span2DT_TryCopyTo2D() { 4, 5, 6 } }; + // Same as above, but copying to a 2D array with the safe TryCopyTo method Span2D span2d = new Span2D(array); int[,] target = new int[2, 3]; Assert.IsTrue(span2d.TryCopyTo(target)); Assert.IsFalse(span2d.TryCopyTo(Span2D.Empty)); + + int[,] expected = + { + { 1, 2, 3 }, + { 4, 5, 6 } + }; + + CollectionAssert.AreEqual(target, expected); } [TestCategory("Span2DT")] [TestMethod] public unsafe void Test_Span2DT_GetPinnableReference() { + // Here we test that a ref from an empty Span2D returns a null ref Assert.IsTrue(Unsafe.AreSame( ref Unsafe.AsRef(null), ref Span2D.Empty.GetPinnableReference())); @@ -497,6 +538,7 @@ ref Unsafe.AsRef(null), ref int r0 = ref span2d.GetPinnableReference(); + // Here we test that GetPinnableReference returns a ref to the first array element Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0])); } @@ -504,6 +546,7 @@ ref Unsafe.AsRef(null), [TestMethod] public unsafe void Test_Span2DT_DangerousGetReference() { + // Same as above, but using DangerousGetReference instead (faster, no conditional check) Assert.IsTrue(Unsafe.AreSame( ref Unsafe.AsRef(null), ref Span2D.Empty.DangerousGetReference())); @@ -531,6 +574,9 @@ public void Test_Span2DT_Slice_1() { 4, 5, 6 } }; + // Here we have a number of tests that just take an initial 2D array, create a Span2D, + // perform a number of slicing operations and then validate the parameters for the resulting + // instances, and that the indexer works correctly and maps to the right original elements. Span2D span2d = new Span2D(array); Span2D slice1 = span2d.Slice(1, 1, 1, 2); @@ -550,6 +596,7 @@ public void Test_Span2DT_Slice_1() Assert.AreEqual(slice2[1, 0], 5); Assert.AreEqual(slice2[1, 1], 6); + // Some checks for invalid arguments Assert.ThrowsException(() => new Span2D(array).Slice(-1, 1, 1, 1)); Assert.ThrowsException(() => new Span2D(array).Slice(1, -1, 1, 1)); Assert.ThrowsException(() => new Span2D(array).Slice(1, 1, 1, -1)); @@ -571,6 +618,7 @@ public void Test_Span2DT_Slice_2() Span2D span2d = new Span2D(array); + // Same as above, but with some different slicing Span2D slice1 = span2d.Slice(0, 0, 2, 2); Assert.AreEqual(slice1.Length, 4); @@ -608,6 +656,12 @@ public void Test_Span2DT_GetRowSpan() Span2D span2d = new Span2D(array); + // Here we create a Span2D from a 2D array and want to get a Span from + // a specific row. This is only supported on runtimes with fast Span support + // for the same reason mentioned in the Memory2D tests (we need the Span + // constructor that only takes a target ref). Then we just get some references + // to items in this span and compare them against references into the original + // 2D array to ensure they match and point to the correct elements from there. Span span = span2d.GetRowSpan(1); Assert.IsTrue(Unsafe.AreSame( @@ -634,6 +688,11 @@ public void Test_Span2DT_TryGetSpan_1() Span2D span2d = new Span2D(array); + // This API tries to get a Span for the entire contents of Span2D. + // This only works on runtimes if the underlying data is contiguous + // and of a size that can fit into a single Span. In this specific test, + // this is not expected to work on UWP because it can't create a Span + // from a 2D array (reasons explained in the comments for the test above). bool success = span2d.TryGetSpan(out Span span); #if WINDOWS_UWP @@ -656,6 +715,8 @@ public void Test_Span2DT_TryGetSpan_2() { 4, 5, 6 } }; + // Same as above, but this will always fail because we're creating + // a Span2D wrapping non contiguous data (the pitch is not 0). Span2D span2d = new Span2D(array, 0, 0, 2, 2); bool success = span2d.TryGetSpan(out Span span); @@ -674,6 +735,8 @@ public void Test_Span2DT_ToArray_1() { 4, 5, 6 } }; + // Here we create a Span2D and verify that ToArray() produces + // a 2D array that is identical to the original one being wrapped. Span2D span2d = new Span2D(array); int[,] copy = span2d.ToArray(); @@ -694,6 +757,7 @@ public void Test_Span2DT_ToArray_2() { 4, 5, 6 } }; + // Same as above, but with extra initial slicing Span2D span2d = new Span2D(array, 0, 0, 2, 2); int[,] copy = span2d.ToArray(); @@ -723,6 +787,7 @@ public void Test_Span2DT_Equals() Span2D span2d = new Span2D(array); + // Span2D.Equals always throw (this mirrors the behavior of Span.Equals) _ = span2d.Equals(null); } @@ -739,6 +804,7 @@ public void Test_Span2DT_GetHashCode() Span2D span2d = new Span2D(array); + // Same as above, this always throws _ = span2d.GetHashCode(); } @@ -754,6 +820,7 @@ public void Test_Span2DT_ToString() Span2D span2d = new Span2D(array); + // Verify that we get the nicely formatted string string text = span2d.ToString(); const string expected = "Microsoft.Toolkit.HighPerformance.Memory.Span2D[2, 3]"; @@ -771,6 +838,8 @@ public void Test_Span2DT_opEquals() { 4, 5, 6 } }; + // Create two Span2D instances wrapping the same array with the same + // parameters, and verify that the equality operators work correctly. Span2D span2d_1 = new Span2D(array); Span2D span2d_2 = new Span2D(array); @@ -778,6 +847,7 @@ public void Test_Span2DT_opEquals() Assert.IsFalse(span2d_1 == Span2D.Empty); Assert.IsTrue(Span2D.Empty == Span2D.Empty); + // Same as above, but verify that a sliced span is not reported as equal Span2D span2d_3 = new Span2D(array, 0, 0, 2, 2); Assert.IsFalse(span2d_1 == span2d_3); @@ -794,6 +864,8 @@ public void Test_Span2DT_ImplicitCast() { 4, 5, 6 } }; + // Verify that an explicit constructor and the implicit conversion + // operator generate an identical Span2D instance from the array. Span2D span2d_1 = array; Span2D span2d_2 = new Span2D(array); @@ -810,6 +882,7 @@ public void Test_Span2DT_GetRow() { 4, 5, 6 } }; + // Get a target row and verify the contents match with our data RefEnumerable enumerable = new Span2D(array).GetRow(1); int[] expected = { 4, 5, 6 }; @@ -831,6 +904,7 @@ public unsafe void Test_Span2DT_Pointer_GetRow() 4, 5, 6 }; + // Same as above, but with a Span2D wrapping a raw pointer RefEnumerable enumerable = new Span2D(array, 2, 3, 0).GetRow(1); int[] expected = { 4, 5, 6 }; @@ -852,6 +926,7 @@ public void Test_Span2DT_GetColumn() { 4, 5, 6 } }; + // Same as above, but getting a column instead RefEnumerable enumerable = new Span2D(array).GetColumn(2); int[] expected = { 3, 6 }; @@ -873,6 +948,7 @@ public unsafe void Test_Span2DT_Pointer_GetColumn() 4, 5, 6 }; + // Same as above, but wrapping a raw pointer RefEnumerable enumerable = new Span2D(array, 2, 3, 0).GetColumn(2); int[] expected = { 3, 6 }; @@ -897,8 +973,17 @@ public void Test_Span2DT_GetEnumerator() int[] result = new int[4]; int i = 0; - foreach (var item in new Span2D(array, 0, 1, 2, 2)) + // Here we want to test the Span2D enumerator. We create a Span2D instance over + // a given section of the initial 2D array, then iterate over it and store the items + // into a temporary array. We then just compare the contents to ensure they match. + foreach (ref var item in new Span2D(array, 0, 1, 2, 2)) { + // Check the reference to ensure it points to the right original item + Assert.IsTrue(Unsafe.AreSame( + ref array[i / 2, (i % 2) + 1], + ref item)); + + // Also store the value to compare it later (redundant, but just in case) result[i++] = item; } @@ -920,8 +1005,14 @@ public unsafe void Test_Span2DT_Pointer_GetEnumerator() int[] result = new int[4]; int i = 0; - foreach (var item in new Span2D(array + 1, 2, 2, 1)) + // Same test as above, but wrapping a raw pointer + foreach (ref var item in new Span2D(array + 1, 2, 2, 1)) { + // Check the reference again + Assert.IsTrue(Unsafe.AreSame( + ref Unsafe.AsRef(&array[((i / 2) * 3) + (i % 2) + 1]), + ref item)); + result[i++] = item; } @@ -936,6 +1027,7 @@ public void Test_Span2DT_GetEnumerator_Empty() { var enumerator = Span2D.Empty.GetEnumerator(); + // Ensure that an enumerator from an empty Span2D can't move next Assert.IsFalse(enumerator.MoveNext()); } } From 151f38a30d7368ad7ace87bb3dc59f415dcd88b8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 31 Oct 2020 19:10:44 +0100 Subject: [PATCH 190/200] Fixed bugs in unit tests --- .../Memory/Test_ReadOnlyMemory2D{T}.cs | 10 +++++++--- .../Memory/Test_ReadOnlySpan2D{T}.cs | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs index e161e8f02a8..4e34d0d3561 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs @@ -61,7 +61,9 @@ public void Test_ReadOnlyMemory2DT_Array1DConstructor() Assert.AreEqual(memory2d.Span[0, 0], 2); Assert.AreEqual(memory2d.Span[1, 1], 6); - Assert.ThrowsException(() => new ReadOnlyMemory2D(new string[1], 1, 1)); + // Here we check to ensure a covariant array conversion is allowed for ReadOnlyMemory2D + _ = new ReadOnlyMemory2D(new string[1], 1, 1); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, -99, 1, 1, 1)); Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, -10, 1, 1)); Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, 1, 1, -1)); @@ -88,7 +90,7 @@ public void Test_ReadOnlyMemory2DT_Array2DConstructor_1() Assert.AreEqual(memory2d.Span[0, 1], 2); Assert.AreEqual(memory2d.Span[1, 2], 6); - Assert.ThrowsException(() => new ReadOnlyMemory2D(new string[1, 2])); + _ = new ReadOnlyMemory2D(new string[1, 2]); } [TestCategory("ReadOnlyMemory2DT")] @@ -110,7 +112,9 @@ public void Test_ReadOnlyMemory2DT_Array2DConstructor_2() Assert.AreEqual(memory2d.Span[0, 0], 2); Assert.AreEqual(memory2d.Span[1, 1], 6); - Assert.ThrowsException(() => new ReadOnlyMemory2D(new string[1, 2], 0, 0, 2, 2)); + _ = new ReadOnlyMemory2D(new string[1, 2]); + + Assert.ThrowsException(() => new ReadOnlyMemory2D(new string[1, 2], 0, 0, 2, 2)); } [TestCategory("ReadOnlyMemory2DT")] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs index 537de8b8f5e..a34abc34574 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs @@ -112,7 +112,9 @@ public void Test_ReadOnlySpan2DT_Array1DConstructor() Assert.AreEqual(span2d[0, 0], 2); Assert.AreEqual(span2d[1, 1], 6); - Assert.ThrowsException(() => new ReadOnlySpan2D(new string[1], 1, 1)); + // Same for ReadOnlyMemory2D, we need to check that covariant array conversions are allowed + _ = new ReadOnlySpan2D(new string[1], 1, 1); + Assert.ThrowsException(() => new ReadOnlySpan2D(array, -99, 1, 1, 1)); Assert.ThrowsException(() => new ReadOnlySpan2D(array, 0, -10, 1, 1)); Assert.ThrowsException(() => new ReadOnlySpan2D(array, 0, 1, 1, -1)); @@ -139,7 +141,7 @@ public void Test_ReadOnlySpan2DT_Array2DConstructor_1() Assert.AreEqual(span2d[0, 1], 2); Assert.AreEqual(span2d[1, 2], 6); - Assert.ThrowsException(() => new ReadOnlySpan2D(new string[1, 2])); + _ = new ReadOnlySpan2D(new string[1, 2]); } [TestCategory("ReadOnlySpan2DT")] @@ -161,7 +163,9 @@ public void Test_ReadOnlySpan2DT_Array2DConstructor_2() Assert.AreEqual(span2d[0, 0], 2); Assert.AreEqual(span2d[1, 1], 6); - Assert.ThrowsException(() => new ReadOnlySpan2D(new string[1, 2], 0, 0, 2, 2)); + _ = new ReadOnlySpan2D(new string[1, 2]); + + Assert.ThrowsException(() => new ReadOnlySpan2D(new string[1, 2], 0, 0, 2, 2)); } [TestCategory("ReadOnlySpan2DT")] From 20bd65f89d3a599cc4aae3e554d36c204685820e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 31 Oct 2020 20:04:44 +0100 Subject: [PATCH 191/200] Fixed bug in ReadOnlyRefEnumerable implicit cast --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index 3e6f55f56ba..b8bfe42cc6d 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -316,7 +316,7 @@ public static implicit operator ReadOnlyRefEnumerable(RefEnumerable enumer #if SPAN_RUNTIME_SUPPORT return new ReadOnlyRefEnumerable(enumerable.Span, enumerable.Step, enumerable.Position); #else - return new ReadOnlyRefEnumerable(enumerable.Instance, enumerable.Offset, enumerable.Length, enumerable.Position); + return new ReadOnlyRefEnumerable(enumerable.Instance, enumerable.Offset, enumerable.Length, enumerable.Step, enumerable.Position); #endif } From f3e7e00756eba1e46d21539f6ef9ed30959ec1ab Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 1 Nov 2020 01:13:10 +0100 Subject: [PATCH 192/200] Minor code refactoring --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 162 ++++++++++------- .../Enumerables/RefEnumerable{T}.cs | 166 ++++++++++++------ 2 files changed, 210 insertions(+), 118 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index b8bfe42cc6d..2eff0897662 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -20,7 +20,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables /// A that iterates readonly items from arbitrary memory locations. /// /// The type of items to enumerate. - public ref struct ReadOnlyRefEnumerable + public readonly ref struct ReadOnlyRefEnumerable { #if SPAN_RUNTIME_SUPPORT /// @@ -51,24 +51,17 @@ public ref struct ReadOnlyRefEnumerable /// The distance refers to items, not byte offset. private readonly int step; - /// - /// The current position in the sequence. - /// - private int position; - #if SPAN_RUNTIME_SUPPORT /// /// Initializes a new instance of the struct. /// /// The instance pointing to the first item in the target memory area. /// The distance between items in the sequence to enumerate. - /// The current position in the sequence. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ReadOnlyRefEnumerable(ReadOnlySpan span, int step, int position) + private ReadOnlyRefEnumerable(ReadOnlySpan span, int step) { this.span = span; this.step = step; - this.position = position; } /// @@ -82,27 +75,8 @@ internal ReadOnlyRefEnumerable(in T reference, int length, int step) { this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length); this.step = step; - this.position = -1; } #else - /// - /// Initializes a new instance of the struct. - /// - /// The target instance. - /// The initial offset within . - /// The number of items in the sequence. - /// The distance between items in the sequence to enumerate. - /// The current position in the sequence. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int step, int position) - { - this.instance = instance; - this.offset = offset; - this.length = length; - this.step = step; - this.position = position; - } - /// /// Initializes a new instance of the struct. /// @@ -117,42 +91,19 @@ internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int this.offset = offset; this.length = length; this.step = step; - this.position = -1; } #endif /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ReadOnlyRefEnumerable GetEnumerator() => this; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { -#if SPAN_RUNTIME_SUPPORT - return ++this.position < this.span.Length; -#else - return ++this.position < this.length; -#endif - } - - /// - public readonly ref readonly T Current + public Enumerator GetEnumerator() { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { #if SPAN_RUNTIME_SUPPORT - ref T r0 = ref this.span.DangerousGetReference(); + return new Enumerator(this.span, this.step); #else - ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); + return new Enumerator(this.instance, this.offset, this.length, this.step); #endif - nint offset = (nint)(uint)this.position * (nint)(uint)this.step; - ref T ri = ref Unsafe.Add(ref r0, offset); - - return ref ri; - } } /// @@ -162,7 +113,7 @@ public readonly ref readonly T Current /// /// Thrown when is shorter than the source instance. /// - public readonly void CopyTo(RefEnumerable destination) + public void CopyTo(RefEnumerable destination) { #if SPAN_RUNTIME_SUPPORT if (this.step == 1) @@ -205,7 +156,7 @@ public readonly void CopyTo(RefEnumerable destination) /// /// The target of the copy operation. /// Whether or not the operation was successful. - public readonly bool TryCopyTo(RefEnumerable destination) + public bool TryCopyTo(RefEnumerable destination) { #if SPAN_RUNTIME_SUPPORT int @@ -234,7 +185,7 @@ public readonly bool TryCopyTo(RefEnumerable destination) /// /// Thrown when is shorter than the source instance. /// - public readonly void CopyTo(Span destination) + public void CopyTo(Span destination) { #if SPAN_RUNTIME_SUPPORT if (this.step == 1) @@ -265,7 +216,7 @@ public readonly void CopyTo(Span destination) /// /// The target of the copy operation. /// Whether or not the operation was successful. - public readonly bool TryCopyTo(Span destination) + public bool TryCopyTo(Span destination) { #if SPAN_RUNTIME_SUPPORT int length = this.span.Length; @@ -285,7 +236,7 @@ public readonly bool TryCopyTo(Span destination) /// [Pure] - public readonly T[] ToArray() + public T[] ToArray() { #if SPAN_RUNTIME_SUPPORT int length = this.span.Length; @@ -314,10 +265,99 @@ public readonly T[] ToArray() public static implicit operator ReadOnlyRefEnumerable(RefEnumerable enumerable) { #if SPAN_RUNTIME_SUPPORT - return new ReadOnlyRefEnumerable(enumerable.Span, enumerable.Step, enumerable.Position); + return new ReadOnlyRefEnumerable(enumerable.Span, enumerable.Step); +#else + return new ReadOnlyRefEnumerable(enumerable.Instance, enumerable.Offset, enumerable.Length, enumerable.Step); +#endif + } + + /// + /// A custom enumerator type to traverse items within a instance. + /// + public ref struct Enumerator + { +#if SPAN_RUNTIME_SUPPORT + /// + private readonly ReadOnlySpan span; #else - return new ReadOnlyRefEnumerable(enumerable.Instance, enumerable.Offset, enumerable.Length, enumerable.Step, enumerable.Position); + /// + private readonly object? instance; + + /// + private readonly IntPtr offset; + + /// + private readonly int length; #endif + + /// + private readonly int step; + + /// + /// The current position in the sequence. + /// + private int position; + +#if SPAN_RUNTIME_SUPPORT + /// + /// Initializes a new instance of the struct. + /// + /// The instance with the info on the items to traverse. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(ReadOnlySpan span, int step) + { + this.span = span; + this.step = step; + this.position = -1; + } +#else + /// + /// Initializes a new instance of the struct. + /// + /// The target instance. + /// The initial offset within . + /// The number of items in the sequence. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(object? instance, IntPtr offset, int length, int step) + { + this.instance = instance; + this.offset = offset; + this.length = length; + this.step = step; + this.position = -1; + } +#endif + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { +#if SPAN_RUNTIME_SUPPORT + return ++this.position < this.span.Length; +#else + return ++this.position < this.length; +#endif + } + + /// + public readonly ref readonly T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref this.span.DangerousGetReference(); +#else + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); +#endif + nint offset = (nint)(uint)this.position * (nint)(uint)this.step; + ref T ri = ref Unsafe.Add(ref r0, offset); + + return ref ri; + } + } } /// diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 607a9cbe739..fae37dc76fc 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -20,7 +20,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables /// A that iterates items from arbitrary memory locations. /// /// The type of items to enumerate. - public ref struct RefEnumerable + public readonly ref struct RefEnumerable { #if SPAN_RUNTIME_SUPPORT /// @@ -51,11 +51,6 @@ public ref struct RefEnumerable /// The distance refers to items, not byte offset. internal readonly int Step; - /// - /// The current position in the sequence. - /// - internal int Position; - #if SPAN_RUNTIME_SUPPORT /// /// Initializes a new instance of the struct. @@ -68,8 +63,6 @@ internal RefEnumerable(ref T reference, int length, int step) { Span = MemoryMarshal.CreateSpan(ref reference, length); Step = step; - - this.Position = -1; } #else /// @@ -86,56 +79,25 @@ internal RefEnumerable(object? instance, IntPtr offset, int length, int step) Offset = offset; Length = length; Step = step; - - this.Position = -1; } #endif /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly RefEnumerable GetEnumerator() => this; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() + public Enumerator GetEnumerator() { #if SPAN_RUNTIME_SUPPORT - return ++this.Position < this.Span.Length; -#else - return ++this.Position < this.Length; -#endif - } - - /// - public readonly ref T Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { -#if SPAN_RUNTIME_SUPPORT - ref T r0 = ref this.Span.DangerousGetReference(); + return new Enumerator(this.Span, this.Step); #else - ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); + return new Enumerator(this.Instance, this.Offset, this.Length, this.Step); #endif - - // Here we just offset by shifting down as if we were traversing a 2D array with a - // a single column, with the width of each row represented by the step, the height - // represented by the current position, and with only the first element of each row - // being inspected. We can perform all the indexing operations in this type as nint, - // as the maximum offset is guaranteed never to exceed the maximum value, since on - // 32 bit architectures it's not possible to allocate that much memory anyway. - nint offset = (nint)(uint)this.Position * (nint)(uint)this.Step; - ref T ri = ref Unsafe.Add(ref r0, offset); - - return ref ri; - } } /// /// Clears the contents of the current instance. /// - public readonly void Clear() + public void Clear() { #if SPAN_RUNTIME_SUPPORT // Fast path for contiguous items @@ -163,7 +125,7 @@ public readonly void Clear() /// /// Thrown when is shorter than the source instance. /// - public readonly void CopyTo(RefEnumerable destination) + public void CopyTo(RefEnumerable destination) { #if SPAN_RUNTIME_SUPPORT if (this.Step == 1) @@ -206,7 +168,7 @@ public readonly void CopyTo(RefEnumerable destination) /// /// The target of the copy operation. /// Whether or not the operation was successful. - public readonly bool TryCopyTo(RefEnumerable destination) + public bool TryCopyTo(RefEnumerable destination) { #if SPAN_RUNTIME_SUPPORT int @@ -235,7 +197,7 @@ public readonly bool TryCopyTo(RefEnumerable destination) /// /// Thrown when is shorter than the source instance. /// - public readonly void CopyTo(Span destination) + public void CopyTo(Span destination) { #if SPAN_RUNTIME_SUPPORT if (this.Step == 1) @@ -266,7 +228,7 @@ public readonly void CopyTo(Span destination) /// /// The target of the copy operation. /// Whether or not the operation was successful. - public readonly bool TryCopyTo(Span destination) + public bool TryCopyTo(Span destination) { #if SPAN_RUNTIME_SUPPORT int length = this.Span.Length; @@ -291,7 +253,7 @@ public readonly bool TryCopyTo(Span destination) /// /// Thrown when the current is shorter than the source instance. /// - internal readonly void CopyFrom(ReadOnlySpan source) + internal void CopyFrom(ReadOnlySpan source) { #if SPAN_RUNTIME_SUPPORT if (this.Step == 1) @@ -323,7 +285,7 @@ internal readonly void CopyFrom(ReadOnlySpan source) /// /// The source instance. /// Whether or not the operation was successful. - public readonly bool TryCopyFrom(ReadOnlySpan source) + public bool TryCopyFrom(ReadOnlySpan source) { #if SPAN_RUNTIME_SUPPORT int length = this.Span.Length; @@ -345,11 +307,7 @@ public readonly bool TryCopyFrom(ReadOnlySpan source) /// Fills the elements of this with a specified value. /// /// The value to assign to each element of the instance. - /// - /// This method will always return the whole sequence from the start, ignoring the - /// current position in case the sequence has already been enumerated in part. - /// - public readonly void Fill(T value) + public void Fill(T value) { #if SPAN_RUNTIME_SUPPORT if (this.Step == 1) @@ -376,11 +334,9 @@ public readonly void Fill(T value) /// /// This method will allocate a new array, so only /// use it if you really need to copy the target items in a new memory location. - /// Additionally, this method will always return the whole sequence from the start, - /// ignoring the current position in case the sequence has already been enumerated in part. /// [Pure] - public readonly T[] ToArray() + public T[] ToArray() { #if SPAN_RUNTIME_SUPPORT int length = this.Span.Length; @@ -401,6 +357,102 @@ public readonly T[] ToArray() return array; } + /// + /// A custom enumerator type to traverse items within a instance. + /// + public ref struct Enumerator + { +#if SPAN_RUNTIME_SUPPORT + /// + private readonly Span span; +#else + /// + private readonly object? instance; + + /// + private readonly IntPtr offset; + + /// + private readonly int length; +#endif + + /// + private readonly int step; + + /// + /// The current position in the sequence. + /// + private int position; + +#if SPAN_RUNTIME_SUPPORT + /// + /// Initializes a new instance of the struct. + /// + /// The instance with the info on the items to traverse. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(Span span, int step) + { + this.span = span; + this.step = step; + this.position = -1; + } +#else + /// + /// Initializes a new instance of the struct. + /// + /// The target instance. + /// The initial offset within . + /// The number of items in the sequence. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(object? instance, IntPtr offset, int length, int step) + { + this.instance = instance; + this.offset = offset; + this.length = length; + this.step = step; + this.position = -1; + } +#endif + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { +#if SPAN_RUNTIME_SUPPORT + return ++this.position < this.span.Length; +#else + return ++this.position < this.length; +#endif + } + + /// + public readonly ref T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref this.span.DangerousGetReference(); +#else + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); +#endif + + // Here we just offset by shifting down as if we were traversing a 2D array with a + // a single column, with the width of each row represented by the step, the height + // represented by the current position, and with only the first element of each row + // being inspected. We can perform all the indexing operations in this type as nint, + // as the maximum offset is guaranteed never to exceed the maximum value, since on + // 32 bit architectures it's not possible to allocate that much memory anyway. + nint offset = (nint)(uint)this.position * (nint)(uint)this.step; + ref T ri = ref Unsafe.Add(ref r0, offset); + + return ref ri; + } + } + } + /// /// Throws an when the target span is too short. /// From bc6f70bde05be362f42ec708800dc8a9223a8377 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 1 Nov 2020 16:39:46 +0100 Subject: [PATCH 193/200] Minor code tweaks for clarity --- .../Helpers/Internals/RefEnumerableHelper.cs | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs index 4c3a96a741a..2844a34fa8b 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs @@ -66,8 +66,8 @@ public static void Clear(ref T r0, nint length, nint step) /// The source reference to copy from. /// The target reference to copy to. /// The total number of items to copy. - /// The step between consecutive items in the memory area pointed to by . - public static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, nint step) + /// The step between consecutive items in the memory area pointed to by . + public static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep) { nint sourceOffset = 0, @@ -76,28 +76,28 @@ public static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, while (length >= 8) { Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); - Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += step); - Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += step); - Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += step); - Unsafe.Add(ref destinationRef, destinationOffset + 4) = Unsafe.Add(ref sourceRef, sourceOffset += step); - Unsafe.Add(ref destinationRef, destinationOffset + 5) = Unsafe.Add(ref sourceRef, sourceOffset += step); - Unsafe.Add(ref destinationRef, destinationOffset + 6) = Unsafe.Add(ref sourceRef, sourceOffset += step); - Unsafe.Add(ref destinationRef, destinationOffset + 7) = Unsafe.Add(ref sourceRef, sourceOffset += step); + Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 4) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 5) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 6) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 7) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); length -= 8; - sourceOffset += step; + sourceOffset += sourceStep; destinationOffset += 8; } if (length >= 4) { Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); - Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += step); - Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += step); - Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += step); + Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); length -= 4; - sourceOffset += step; + sourceOffset += sourceStep; destinationOffset += 4; } @@ -106,7 +106,7 @@ public static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); length -= 1; - sourceOffset += step; + sourceOffset += sourceStep; destinationOffset += 1; } } @@ -166,14 +166,14 @@ public static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, /// /// Copies a sequence of discontiguous items from one memory area to another. This mirrors - /// , but refers to instead. + /// , but refers to instead. /// /// The type of items to copy. /// The source reference to copy from. /// The target reference to copy to. /// The total number of items to copy. - /// The step between consecutive items in the memory area pointed to by . - public static void CopyFrom(ref T sourceRef, ref T destinationRef, nint length, nint step) + /// The step between consecutive items in the memory area pointed to by . + public static void CopyFrom(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep) { nint sourceOffset = 0, @@ -182,29 +182,29 @@ public static void CopyFrom(ref T sourceRef, ref T destinationRef, nint lengt while (length >= 8) { Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); - Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 1); - Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 2); - Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 3); - Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 4); - Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 5); - Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 6); - Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 7); + Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 1); + Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 2); + Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 3); + Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 4); + Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 5); + Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 6); + Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 7); length -= 8; sourceOffset += 8; - destinationOffset += step; + destinationOffset += sourceStep; } if (length >= 4) { Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); - Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 1); - Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 2); - Unsafe.Add(ref destinationRef, destinationOffset += step) = Unsafe.Add(ref sourceRef, sourceOffset + 3); + Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 1); + Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 2); + Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 3); length -= 4; sourceOffset += 4; - destinationOffset += step; + destinationOffset += sourceStep; } while (length > 0) @@ -213,7 +213,7 @@ public static void CopyFrom(ref T sourceRef, ref T destinationRef, nint lengt length -= 1; sourceOffset += 1; - destinationOffset += step; + destinationOffset += sourceStep; } } From 7a65a29ef8c0570c7c66a20cf2bb45d72a3066b2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 3 Nov 2020 23:08:57 +0100 Subject: [PATCH 194/200] Added tests for off-by-one Get[Row|Column] for 2D arrays --- .../Extensions/Test_ArrayExtensions.2D.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs index 8215eba7a12..66aab7c3fb6 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs @@ -194,6 +194,7 @@ public void Test_ArrayExtensions_2D_GetRow_Rectangle() Assert.AreSame(new int[1, 0].GetRow(0).ToArray(), Array.Empty()); Assert.ThrowsException(() => array.GetRow(-1)); + Assert.ThrowsException(() => array.GetRow(3)); Assert.ThrowsException(() => array.GetRow(20)); } @@ -221,6 +222,7 @@ public void Test_ArrayExtensions_2D_GetColumn_Rectangle() CollectionAssert.AreEqual(array.GetColumn(1).ToArray(), new[] { 2, 6, 10 }); Assert.ThrowsException(() => array.GetColumn(-1)); + Assert.ThrowsException(() => array.GetColumn(4)); Assert.ThrowsException(() => array.GetColumn(20)); } From 6bb9e5a52e2fe199e0e2c2e2092183534a5a4ee8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 3 Nov 2020 23:20:52 +0100 Subject: [PATCH 195/200] Improved some unit tests --- .../Extensions/Test_ReadOnlySpanExtensions.cs | 34 +++++++++++++------ .../Extensions/Test_SpanExtensions.cs | 34 +++++++++++++------ 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs index 8035f16cf5f..32d51fbb4a8 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs @@ -272,30 +272,42 @@ public void Test_ReadOnlySpanExtensions_Tokenize_CommaWithMissingValues() [TestMethod] public void Test_ReadOnlySpanExtensions_CopyTo_RefEnumerable() { - int[,] array1 = new int[4, 5]; + int[,] array = new int[4, 5]; - ReadOnlySpan values = new[] { 10, 20, 30, 40, 50 }; + ReadOnlySpan + values1 = new[] { 10, 20, 30, 40, 50 }, + values2 = new[] { 11, 22, 33, 44, 55 }; // Copy a span to a target row and column with valid lengths - values.CopyTo(array1.GetRow(0)); - values.Slice(0, 4).CopyTo(array1.GetColumn(1)); + values1.CopyTo(array.GetRow(0)); + values2.Slice(0, 4).CopyTo(array.GetColumn(1)); + + int[,] result = + { + { 10, 11, 30, 40, 50 }, + { 0, 22, 0, 0, 0 }, + { 0, 33, 0, 0, 0 }, + { 0, 44, 0, 0, 0 } + }; + + CollectionAssert.AreEqual(array, result); // Try to copy to a valid row and an invalid column (too short for the source span) - bool shouldBeTrue = values.TryCopyTo(array1.GetRow(2)); - bool shouldBeFalse = values.TryCopyTo(array1.GetColumn(1)); + bool shouldBeTrue = values1.TryCopyTo(array.GetRow(2)); + bool shouldBeFalse = values2.TryCopyTo(array.GetColumn(3)); Assert.IsTrue(shouldBeTrue); Assert.IsFalse(shouldBeFalse); - int[,] result = + result = new[,] { - { 10, 10, 30, 40, 50 }, - { 0, 20, 0, 0, 0 }, + { 10, 11, 30, 40, 50 }, + { 0, 22, 0, 0, 0 }, { 10, 20, 30, 40, 50 }, - { 0, 40, 0, 0, 0 } + { 0, 44, 0, 0, 0 } }; - CollectionAssert.AreEqual(array1, result); + CollectionAssert.AreEqual(array, result); } } } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_SpanExtensions.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_SpanExtensions.cs index 05c001f6a5f..4e6753173d2 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_SpanExtensions.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_SpanExtensions.cs @@ -173,30 +173,42 @@ public void Test_SpanExtensions_Enumerate_Empty() [TestMethod] public void Test_SpanExtensions_CopyTo_RefEnumerable() { - int[,] array1 = new int[4, 5]; + int[,] array = new int[4, 5]; - int[] values = { 10, 20, 30, 40, 50 }; + int[] + values1 = { 10, 20, 30, 40, 50 }, + values2 = { 11, 22, 33, 44, 55 }; // Copy a span to a target row and column with valid lengths - values.AsSpan().CopyTo(array1.GetRow(0)); - values.AsSpan(0, 4).CopyTo(array1.GetColumn(1)); + values1.AsSpan().CopyTo(array.GetRow(0)); + values2.AsSpan(0, 4).CopyTo(array.GetColumn(1)); + + int[,] result = + { + { 10, 11, 30, 40, 50 }, + { 0, 22, 0, 0, 0 }, + { 0, 33, 0, 0, 0 }, + { 0, 44, 0, 0, 0 } + }; + + CollectionAssert.AreEqual(array, result); // Try to copy to a valid row and an invalid column (too short for the source span) - bool shouldBeTrue = values.AsSpan().TryCopyTo(array1.GetRow(2)); - bool shouldBeFalse = values.AsSpan().TryCopyTo(array1.GetColumn(1)); + bool shouldBeTrue = values1.AsSpan().TryCopyTo(array.GetRow(2)); + bool shouldBeFalse = values2.AsSpan().TryCopyTo(array.GetColumn(3)); Assert.IsTrue(shouldBeTrue); Assert.IsFalse(shouldBeFalse); - int[,] result = + result = new[,] { - { 10, 10, 30, 40, 50 }, - { 0, 20, 0, 0, 0 }, + { 10, 11, 30, 40, 50 }, + { 0, 22, 0, 0, 0 }, { 10, 20, 30, 40, 50 }, - { 0, 40, 0, 0, 0 } + { 0, 44, 0, 0, 0 } }; - CollectionAssert.AreEqual(array1, result); + CollectionAssert.AreEqual(array, result); } } } From 7b04c7d4a7846ddaff3f6686307984c8970fb696 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 3 Nov 2020 23:40:27 +0100 Subject: [PATCH 196/200] Added more tests for invalid Memory2D constructors --- .../Memory/Test_Memory2D{T}.cs | 15 +++++++++++++++ .../Memory/Test_ReadOnlyMemory2D{T}.cs | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs index 55652fd4644..3ebe61d5972 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_Memory2D{T}.cs @@ -79,6 +79,9 @@ public void Test_Memory2DT_Array1DConstructor() Assert.ThrowsException(() => new Memory2D(array, 0, -10, 1, 1)); Assert.ThrowsException(() => new Memory2D(array, 0, 1, 1, -1)); Assert.ThrowsException(() => new Memory2D(array, 0, 1, -100, 1)); + Assert.ThrowsException(() => new Memory2D(array, 0, 2, 4, 0)); + Assert.ThrowsException(() => new Memory2D(array, 0, 3, 3, 0)); + Assert.ThrowsException(() => new Memory2D(array, 1, 2, 3, 0)); Assert.ThrowsException(() => new Memory2D(array, 0, 10, 1, 120)); } @@ -158,6 +161,7 @@ public void Test_Memory2DT_Array3DConstructor_1() // A couple of tests for invalid parameters, ie. layers out of range Assert.ThrowsException(() => new Memory2D(array, -1)); + Assert.ThrowsException(() => new Memory2D(array, 2)); Assert.ThrowsException(() => new Memory2D(array, 20)); } @@ -196,6 +200,10 @@ public void Test_Memory2DT_Array3DConstructor_2() Assert.ThrowsException(() => new Memory2D(array, 1, 1, -1, 1, 1)); Assert.ThrowsException(() => new Memory2D(array, 1, 1, 1, -1, 1)); Assert.ThrowsException(() => new Memory2D(array, 1, 1, 1, 1, -1)); + Assert.ThrowsException(() => new Memory2D(array, 2, 0, 0, 2, 3)); + Assert.ThrowsException(() => new Memory2D(array, 0, 0, 1, 2, 3)); + Assert.ThrowsException(() => new Memory2D(array, 0, 0, 0, 2, 4)); + Assert.ThrowsException(() => new Memory2D(array, 0, 0, 0, 3, 3)); } #if !WINDOWS_UWP @@ -225,6 +233,9 @@ public void Test_Memory2DT_MemoryConstructor() Assert.ThrowsException(() => memory.AsMemory2D(0, -10, 1, 1)); Assert.ThrowsException(() => memory.AsMemory2D(0, 1, 1, -1)); Assert.ThrowsException(() => memory.AsMemory2D(0, 1, -100, 1)); + Assert.ThrowsException(() => memory.AsMemory2D(0, 2, 4, 0)); + Assert.ThrowsException(() => memory.AsMemory2D(0, 3, 3, 0)); + Assert.ThrowsException(() => memory.AsMemory2D(1, 2, 3, 0)); Assert.ThrowsException(() => memory.AsMemory2D(0, 10, 1, 120)); } #endif @@ -270,6 +281,10 @@ public void Test_Memory2DT_Slice_1() Assert.ThrowsException(() => new Memory2D(array).Slice(10, 1, 1, 1)); Assert.ThrowsException(() => new Memory2D(array).Slice(1, 12, 1, 12)); Assert.ThrowsException(() => new Memory2D(array).Slice(1, 1, 55, 1)); + Assert.ThrowsException(() => new Memory2D(array).Slice(0, 0, 2, 4)); + Assert.ThrowsException(() => new Memory2D(array).Slice(0, 0, 3, 3)); + Assert.ThrowsException(() => new Memory2D(array).Slice(0, 1, 2, 3)); + Assert.ThrowsException(() => new Memory2D(array).Slice(1, 0, 2, 3)); } [TestCategory("Memory2DT")] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs index 4e34d0d3561..4cbc4c23c86 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlyMemory2D{T}.cs @@ -68,6 +68,9 @@ public void Test_ReadOnlyMemory2DT_Array1DConstructor() Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, -10, 1, 1)); Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, 1, 1, -1)); Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, 1, -100, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, 2, 4, 0)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, 3, 3, 0)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 1, 2, 3, 0)); Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, 10, 1, 120)); } @@ -144,6 +147,7 @@ public void Test_ReadOnlyMemory2DT_Array3DConstructor_1() Assert.ThrowsException(() => new ReadOnlyMemory2D(array, -1)); Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 20)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 2)); } [TestCategory("ReadOnlyMemory2DT")] @@ -176,6 +180,10 @@ public void Test_ReadOnlyMemory2DT_Array3DConstructor_2() Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 1, 1, -1, 1, 1)); Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 1, 1, 1, -1, 1)); Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 1, 1, 1, 1, -1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 2, 0, 0, 2, 3)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, 0, 1, 2, 3)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, 0, 0, 2, 4)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, 0, 0, 3, 3)); } #if !WINDOWS_UWP @@ -201,6 +209,9 @@ public void Test_ReadOnlyMemory2DT_ReadOnlyMemoryConstructor() Assert.ThrowsException(() => memory.AsMemory2D(0, -10, 1, 1)); Assert.ThrowsException(() => memory.AsMemory2D(0, 1, 1, -1)); Assert.ThrowsException(() => memory.AsMemory2D(0, 1, -100, 1)); + Assert.ThrowsException(() => memory.AsMemory2D(0, 2, 4, 0)); + Assert.ThrowsException(() => memory.AsMemory2D(0, 3, 3, 0)); + Assert.ThrowsException(() => memory.AsMemory2D(1, 2, 3, 0)); Assert.ThrowsException(() => memory.AsMemory2D(0, 10, 1, 120)); } #endif @@ -241,6 +252,10 @@ public void Test_ReadOnlyMemory2DT_Slice_1() Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(10, 1, 1, 1)); Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(1, 12, 1, 12)); Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(1, 1, 55, 1)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(0, 0, 2, 4)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(0, 0, 3, 3)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(0, 1, 2, 3)); + Assert.ThrowsException(() => new ReadOnlyMemory2D(array).Slice(1, 0, 2, 3)); } [TestCategory("ReadOnlyMemory2DT")] From 7738d344df1002f6fbd206b4e54524e41a036c2a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 3 Nov 2020 23:42:04 +0100 Subject: [PATCH 197/200] Fixed some typos --- Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs | 6 +++--- .../Memory/ReadOnlyMemory2D{T}.cs | 8 ++++---- .../Memory/ReadOnlySpan2D{T}.cs | 4 ++-- Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs index 12c4803053e..6b3cb0cc82e 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -67,7 +67,7 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// /// Thrown when either or are invalid. /// - /// The total area must match the lenght of . + /// The total area must match the length of . public Memory2D(T[] array, int height, int width) : this(array, 0, height, width, 0) { @@ -320,7 +320,7 @@ public Memory2D(T[,,] array, int depth, int row, int column, int height, int wid /// /// Thrown when either or are invalid. /// - /// The total area must match the lenght of . + /// The total area must match the length of . public Memory2D(MemoryManager memoryManager, int height, int width) : this(memoryManager, 0, height, width, 0) { @@ -396,7 +396,7 @@ public Memory2D(MemoryManager memoryManager, int offset, int height, int widt /// /// Thrown when either or are invalid. /// - /// The total area must match the lenght of . + /// The total area must match the length of . internal Memory2D(Memory memory, int height, int width) : this(memory, 0, height, width, 0) { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 4b8b6b4b352..9b561233c1c 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -61,7 +61,7 @@ namespace Microsoft.Toolkit.HighPerformance.Memory /// /// Thrown when either or are invalid. /// - /// The total area must match the lenght of . + /// The total area must match the length of . public ReadOnlyMemory2D(string text, int height, int width) : this(text, 0, height, width, 0) { @@ -128,7 +128,7 @@ public ReadOnlyMemory2D(string text, int offset, int height, int width, int pitc /// /// Thrown when either or are invalid. /// - /// The total area must match the lenght of . + /// The total area must match the length of . public ReadOnlyMemory2D(T[] array, int height, int width) : this(array, 0, height, width, 0) { @@ -341,7 +341,7 @@ public ReadOnlyMemory2D(T[,,] array, int depth, int row, int column, int height, /// /// Thrown when either or are invalid. /// - /// The total area must match the lenght of . + /// The total area must match the length of . public ReadOnlyMemory2D(MemoryManager memoryManager, int height, int width) : this(memoryManager, 0, height, width, 0) { @@ -417,7 +417,7 @@ public ReadOnlyMemory2D(MemoryManager memoryManager, int offset, int height, /// /// Thrown when either or are invalid. /// - /// The total area must match the lenght of . + /// The total area must match the length of . internal ReadOnlyMemory2D(ReadOnlyMemory memory, int height, int width) : this(memory, 0, height, width, 0) { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 9c22eaf25b8..6ef680eb263 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -146,7 +146,7 @@ internal ReadOnlySpan2D(object? instance, IntPtr offset, int height, int width, /// /// Thrown when either or are invalid. /// - /// The total area must match the lenght of . + /// The total area must match the length of . public ReadOnlySpan2D(T[] array, int height, int width) : this(array, 0, height, width, 0) { @@ -384,7 +384,7 @@ public ReadOnlySpan2D(T[,,] array, int depth, int row, int column, int height, i /// /// Thrown when either or are invalid. /// - /// The total area must match the lenght of . + /// The total area must match the length of . internal ReadOnlySpan2D(ReadOnlySpan span, int height, int width) : this(span, 0, height, width, 0) { diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs index 2f7be520c0e..fe230070c5d 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs @@ -184,7 +184,7 @@ internal Span2D(object? instance, IntPtr offset, int height, int width, int pitc /// /// Thrown when either or are invalid. /// - /// The total area must match the lenght of . + /// The total area must match the length of . public Span2D(T[] array, int height, int width) : this(array, 0, height, width, 0) { @@ -455,7 +455,7 @@ public Span2D(T[,,] array, int depth, int row, int column, int height, int width /// /// Thrown when either or are invalid. /// - /// The total area must match the lenght of . + /// The total area must match the length of . internal Span2D(Span span, int height, int width) : this(span, 0, height, width, 0) { From cd210865596f2f10c2f8265359d2c9ee0ab179e9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 4 Nov 2020 19:31:18 +0100 Subject: [PATCH 198/200] Fixed XML docs for internal helpers --- .../Memory/Internals/OverflowHelper.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs b/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs index 642378e64e3..83576d39197 100644 --- a/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs +++ b/Microsoft.Toolkit.HighPerformance/Memory/Internals/OverflowHelper.cs @@ -21,6 +21,7 @@ internal static class OverflowHelper /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map (the distance between each row). /// Throw when the inputs don't fit in the expected range. + /// The input parameters are assumed to always be positive. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void EnsureIsInNativeIntRange(int height, int width, int pitch) { @@ -53,13 +54,14 @@ public static void EnsureIsInNativeIntRange(int height, int width, int pitch) } /// - /// Ensures that the input parameters will not exceed the maximum native int value when indexing. + /// Ensures that the input parameters will not exceed when indexing. /// /// The height of the 2D memory area to map. /// The width of the 2D memory area to map. /// The pitch of the 2D memory area to map (the distance between each row). /// The area resulting from the given parameters. /// Throw when the inputs don't fit in the expected range. + /// The input parameters are assumed to always be positive. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ComputeInt32Area(int height, int width, int pitch) From 9c90f66e8270935cfce32d00b76982e7090196b6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 5 Nov 2020 00:41:10 +0100 Subject: [PATCH 199/200] Updated package description with new types --- .../Microsoft.Toolkit.HighPerformance.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj index 8ec27230fc6..06c9e584da5 100644 --- a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj +++ b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj @@ -8,12 +8,13 @@ Windows Community Toolkit High Performance .NET Standard This package includes high performance .NET Standard helpers such as: + - Memory2D<T> and Span2D<T>: two types providing fast and allocation-free abstraction over 2D memory areas. - ArrayPoolBufferWriter<T>: an IBufferWriter<T> implementation using pooled arrays, which also supports IMemoryOwner<T>. - MemoryBufferWriter<T>: an IBufferWriter<T>: implementation that can wrap external Memory<T>: instances. - MemoryOwner<T>: an IMemoryOwner<T> implementation with an embedded length and a fast Span<T> accessor. - SpanOwner<T>: a stack-only type with the ability to rent a buffer of a specified length and getting a Span<T> from it. - StringPool: a configurable pool for string instances that be used to minimize allocations when creating multiple strings from char buffers. - - String, array, Span<T>, Memory<T> extensions and more, all focused on high performance. + - String, array, Memory<T>, Span<T> extensions and more, all focused on high performance. - HashCode<T>: a SIMD-enabled extension of HashCode to quickly process sequences of values. - BitHelper: a class with helper methods to perform bit operations on numeric types. - ParallelHelper: helpers to work with parallel code in a highly optimized manner. From 7e67a9c84bb89a1500fd139739ddf8a91fecece2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 5 Nov 2020 01:34:44 +0100 Subject: [PATCH 200/200] Fixed StyleCop warnings in HighPerformance tests --- .../Buffers/Test_StringPool.cs | 26 +++++++++---------- .../Extensions/Test_ArrayExtensions.1D.cs | 2 ++ .../Extensions/Test_ArrayExtensions.2D.cs | 11 -------- .../Test_ReadOnlySpanExtensions.Count.cs | 4 +-- .../Extensions/Test_ReadOnlySpanExtensions.cs | 2 ++ .../Memory/Test_ReadOnlySpan2D{T}.cs | 2 -- 6 files changed, 19 insertions(+), 28 deletions(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_StringPool.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_StringPool.cs index b679609d40a..32f14caf797 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_StringPool.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_StringPool.cs @@ -38,13 +38,13 @@ public void Test_StringPool_Cctor_Ok(int minimumSize, int x, int y, int size) Assert.AreEqual(size, pool.Size); - Array maps = (Array)typeof(StringPool).GetField("maps", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(pool); + Array maps = (Array)typeof(StringPool).GetField("maps", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(pool)!; Assert.AreEqual(x, maps.Length); - Type bucketType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap, Microsoft.Toolkit.HighPerformance"); + Type bucketType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap, Microsoft.Toolkit.HighPerformance")!; - int[] buckets = (int[])bucketType.GetField("buckets", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(maps.GetValue(0)); + int[] buckets = (int[])bucketType.GetField("buckets", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(maps.GetValue(0))!; Assert.AreEqual(y, buckets.Length); } @@ -64,7 +64,7 @@ public void Test_StringPool_Cctor_Fail(int size) } catch (ArgumentOutOfRangeException e) { - var cctor = typeof(StringPool).GetConstructor(new[] { typeof(int) }); + var cctor = typeof(StringPool).GetConstructor(new[] { typeof(int) })!; Assert.AreEqual(cctor.GetParameters()[0].Name, e.ParamName); } @@ -158,7 +158,7 @@ public void Test_StringPool_Add_Overwrite() [MethodImpl(MethodImplOptions.NoInlining)] private static string ToStringNoInlining(object obj) { - return obj.ToString(); + return obj.ToString()!; } [TestCategory("StringPool")] @@ -285,15 +285,15 @@ public void Test_StringPool_GetOrAdd_Overflow() } // Get the buckets - Array maps = (Array)typeof(StringPool).GetField("maps", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(pool); + Array maps = (Array)typeof(StringPool).GetField("maps", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(pool)!; - Type bucketType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap, Microsoft.Toolkit.HighPerformance"); - FieldInfo timestampInfo = bucketType.GetField("timestamp", BindingFlags.Instance | BindingFlags.NonPublic); + Type bucketType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap, Microsoft.Toolkit.HighPerformance")!; + FieldInfo timestampInfo = bucketType.GetField("timestamp", BindingFlags.Instance | BindingFlags.NonPublic)!; // Force the timestamp to be the maximum value, or the test would take too long for (int i = 0; i < maps.LongLength; i++) { - object map = maps.GetValue(i); + object map = maps.GetValue(i)!; timestampInfo.SetValue(map, uint.MaxValue); @@ -305,16 +305,16 @@ public void Test_StringPool_GetOrAdd_Overflow() _ = pool.GetOrAdd(text); - Type heapEntryType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap+HeapEntry, Microsoft.Toolkit.HighPerformance"); + Type heapEntryType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap+HeapEntry, Microsoft.Toolkit.HighPerformance")!; foreach (var map in maps) { // Get the heap for each bucket - Array heapEntries = (Array)bucketType.GetField("heapEntries", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(map); - FieldInfo fieldInfo = heapEntryType.GetField("Timestamp"); + Array heapEntries = (Array)bucketType.GetField("heapEntries", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(map)!; + FieldInfo fieldInfo = heapEntryType.GetField("Timestamp")!; // Extract the array with the timestamps in the heap nodes - uint[] array = heapEntries.Cast().Select(entry => (uint)fieldInfo.GetValue(entry)).ToArray(); + uint[] array = heapEntries.Cast().Select(entry => (uint)fieldInfo.GetValue(entry)!).ToArray(); static bool IsMinHeap(uint[] array) { diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.1D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.1D.cs index 7b12703be13..212d151dbd9 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.1D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.1D.cs @@ -2,6 +2,7 @@ // 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.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -9,6 +10,7 @@ namespace UnitTests.HighPerformance.Extensions { [TestClass] + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1601", Justification = "Partial test class")] public partial class Test_ArrayExtensions { [TestCategory("ArrayExtensions")] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs index 66aab7c3fb6..a25961532a7 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ArrayExtensions.2D.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Extensions; @@ -167,8 +166,6 @@ public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayBottomRightCornerBoundar [TestCategory("ArrayExtensions")] [TestMethod] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] - [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] public void Test_ArrayExtensions_2D_GetRow_Rectangle() { int[,] array = @@ -201,8 +198,6 @@ public void Test_ArrayExtensions_2D_GetRow_Rectangle() [TestCategory("ArrayExtensions")] [TestMethod] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] - [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] public void Test_ArrayExtensions_2D_GetColumn_Rectangle() { int[,] array = @@ -239,8 +234,6 @@ public void Test_ArrayExtensions_2D_GetRow_Empty() [TestCategory("ArrayExtensions")] [TestMethod] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] - [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] public void Test_ArrayExtensions_2D_GetRowOrColumn_Helpers() { int[,] array = @@ -337,8 +330,6 @@ public void Test_ArrayExtensions_2D_GetRowOrColumn_Helpers() [TestCategory("ArrayExtensions")] [TestMethod] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] - [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] public void Test_ArrayExtensions_2D_ReadOnlyGetRowOrColumn_Helpers() { int[,] array = @@ -384,8 +375,6 @@ public void Test_ArrayExtensions_2D_ReadOnlyGetRowOrColumn_Helpers() [TestCategory("ArrayExtensions")] [TestMethod] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] - [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] public void Test_ArrayExtensions_2D_RefEnumerable_Misc() { int[,] array1 = diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs index 10f1591aeab..e06148d04bb 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs @@ -103,7 +103,7 @@ private sealed class Int : IEquatable public Int(int value) => Value = value; - public bool Equals(Int other) + public bool Equals(Int? other) { if (other is null) { @@ -118,7 +118,7 @@ public bool Equals(Int other) return this.Value == other.Value; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return ReferenceEquals(this, obj) || (obj is Int other && Equals(other)); } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs index 32d51fbb4a8..3bea7fcb032 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Extensions; @@ -12,6 +13,7 @@ namespace UnitTests.HighPerformance.Extensions { [TestClass] + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1601", Justification = "Partial test class")] public partial class Test_ReadOnlySpanExtensions { [TestCategory("ReadOnlySpanExtensions")] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs index a34abc34574..e8099e280ab 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Memory/Test_ReadOnlySpan2D{T}.cs @@ -847,8 +847,6 @@ public void Test_ReadOnlySpan2DT_GetEnumerator_Empty() [TestCategory("ReadOnlySpan2DT")] [TestMethod] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] - [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] public void Test_ReadOnlySpan2DT_ReadOnlyRefEnumerable_Misc() { int[,] array1 =