Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2D System.Memory-like primitives (Span2D<T>, Memory2D<T>) #3353

Merged
merged 203 commits into from Nov 5, 2020

Conversation

@Sergio0694
Copy link
Member

@Sergio0694 Sergio0694 commented Jun 17, 2020

The 🦙 (@michael-hawker) asked:

"If I have a 3D array, I want to take one of the slices of the 2D array out of it as a reference...?"

And this is the answer 🚀

PR Type

What kind of change does this PR introduce?

  • Feature

What is the current behavior?

There is no proper way to pass around a 2D memory area in a format that's easy to handle and somewhat similar to how the other System.Memory APIs work (ie. Span<T>, Memory<T>). This is regardless of whether or not the wrapped memory region is contiguous in memory or not.

What is the new behavior?

This PR adds a number of new types and extensions to solve this issue, specifically:

  • Span2D<T>
  • ReadOnlySpan2D<T>
  • Memory2D<T>
  • ReadOnlyMemory2D<T>

And a series of new extensions for 3D arrays as well.
Note that all these 2D types can be used with arrays of any type, eg. they can both be used to wrap a 2D memory region from a 2D array, a 2D slice for a 3D array, or even just to represent a 1D array as a 2D memory region to make it easier to properly access elements by their spatial coordinates.

NOTE: as mentioned above, the wrapped 2D area does not need to be contiguous! See comments in Span2D<T> to see how this is handled. Having this feature gives users much more freedom and flexibility when working with arbitrary arrays (or unmanaged memory too).

API surface (summary)

namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
    public readonly ref struct RefEnumerable<T>
    {
        public readonly Enumerator GetEnumerator();
        public readonly void Clear();
        public readonly void CopyTo(RefEnumerable<T> destination);
        public readonly bool TryCopyTo(RefEnumerable<T> destination);
        public readonly void CopyTo(Span<T> destination);
        public readonly bool TryCopyTo(Span<T> destination);
        public readonly void Fill(T value);
        public readonly T[] ToArray();

        public ref struct Enumerator
        {
            public readonly ref T Current { get; }

            public bool MoveNext();
        }
    }

    public readonly ref struct ReadOnlyRefEnumerable<T>
    {
        public readonly Enumerator GetEnumerator();
        public readonly void CopyTo(RefEnumerable<T> destination);
        public readonly bool TryCopyTo(RefEnumerable<T> destination);
        public readonly void CopyTo(Span<T> destination);
        public readonly bool TryCopyTo(Span<T> destination);
        public readonly T[] ToArray();

        public static implicit operator ReadOnlyRefEnumerable<T>(RefEnumerable<T> enumerable);

        public ref struct Enumerator
        {
            public readonly ref readonly T Current { get; }

            public bool MoveNext();
        }
    }
}

namespace Microsoft.Toolkit.HighPerformance.Extensions
{
    public static class ArrayExtensions
    {
        public static bool IsCovariant<T>(this T[] array);

        public static RefEnumerable<T> GetRow<T>(this T[,] array, int row);
        public static RefEnumerable<T> GetColumn<T>(this T[,] array, int column);
        public static Span<T> GetRowSpan<T>(this T[,] array, int row);
        public static Memory<T> GetRowMemory<T>(this T[,] array, int row);
        public static Memory<T> AsMemory<T>(this T[,]? array);
        public static Span2D<T> AsSpan2D<T>(this T[,]? array);
        public static Span2D<T> AsSpan2D<T>(this T[,]? array, int row, int column, int height, int width);
        public static Memory2D<T> AsMemory2D<T>(this T[,]? array);
        public static Memory2D<T> AsMemory2D<T>(this T[,]? array, int row, int column, int height, int width);
        public static bool IsCovariant<T>(this T[,] array);

        public static ref T DangerousGetReference<T>(this T[,,] array);
        public static ref T DangerousGetReferenceAt<T>(this T[,,] array, int i, int j, int k);
        public static Span<T> AsSpan<T>(this T[,,]? array);
        public static Memory<T> AsMemory<T>(this T[,,]? array);
        public static Span<T> AsSpan<T>(this T[,,] array, int depth);
        public static Memory<T> AsMemory<T>(this T[,,] array, int depth);
        public static Span2D<T> AsSpan2D<T>(this T[,,] array, int depth);
        public static Memory2D<T> AsMemory2D<T>(this T[,,] array, int depth);
        public static int Count<T>(this T[,,] array, T value) where T : IEquatable<T>;
        public static int GetDjb2HashCode<T>(this T[,,] array);
        public static bool IsCovariant<T>(this T[,,] array);
    }

    public static class MemoryExtensions
    {
        public static Memory2D<T> AsMemory2D<T>(this Memory<T> memory, int height, int width);
        public static Memory2D<T> AsMemory2D<T>(this Memory<T> memory, int offset, int height, int width, int pitch);
    }

    public static class ReadOnlyMemoryExtensions
    {
        public static ReadOnlyMemory2D<T> AsMemory2D<T>(this ReadOnlyMemory<T> memory, int height, int width);
        public static ReadOnlyMemory2D<T> AsMemory2D<T>(this ReadOnlyMemory<T> memory, int offset, int height, int width, int pitch);
    }

    public static class ReadOnlySpanExtensions
    {
        public static ReadOnlySpan2D<T> AsMemory2D<T>(this ReadOnlySpan<T> span, int height, int width);
        public static ReadOnlySpan2D<T> AsMemory2D<T>(this ReadOnlySpan<T> span, int offset, int height, int width, int pitch);
        public static void CopyTo<T>(this ReadOnlySpan<T> span, RefEnumerable<T> destination);
        public static bool TryCopyTo<T>(this ReadOnlySpan<T> span, RefEnumerable<T> destination);
    }

    public static class SpanExtensions
    {
        public static Span2D<T> AsMemory2D<T>(this Span<T> span, int height, int width);
        public static Span2D<T> AsMemory2D<T>(this Span<T> span, int offset, int height, int width, int pitch);
        public static void CopyTo<T>(this Span<T> span, RefEnumerable<T> destination);
        public static bool TryCopyTo<T>(this Span<T> span, RefEnumerable<T> destination);
    }
}

namespace Microsoft.Toolkit.HighPerformance.Helpers
{
    public static partial class ParallelHelper
    {
        public static void ForEach<TItem, TAction>(Memory2D<TItem> memory) where TAction : struct, IRefAction<TItem>;
        public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, int minimumActionsPerThread) where TAction : struct, IRefAction<TItem>;
        public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, in TAction action) where TAction : struct, IRefAction<TItem>;
        public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, in TAction action, int minimumActionsPerThread) where TAction : struct, IRefAction<TItem>;
        public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory) where TAction : struct, IInAction<TItem>;
        public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, int minimumActionsPerThread) where TAction : struct, IInAction<TItem>;
        public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, in TAction action) where TAction : struct, IInAction<TItem>;
        public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, in TAction action, int minimumActionsPerThread) where TAction : struct, IInAction<TItem>;
    }
}

namespace Microsoft.Toolkit.HighPerformance.Memory
{
    public readonly struct Memory2D<T> : IEquatable<Memory2D<T>>
    {
        public Memory2D(T[] array, int height, int width);
        public Memory2D(T[] array, int offset, int height, int width, int pitch);
        public Memory2D(T[,]? array);
        public Memory2D(T[,]? array, int row, int column, int height, int width);
        public Memory2D(T[,,] array, int depth);
        public Memory2D(T[,,] array, int depth, int row, int column, int height, int width);
        public Memory2D(MemoryManager<T> memoryManager, int height, int width);
        public Memory2D(MemoryManager<T> memoryManager, int offset, int height, int width, int pitch);

        public static Memory2D<T> DangerousCreate(object instance, ref T value, int height, int width, int pitch);

        public static Memory2D<T> Empty { get; }

        public bool IsEmpty { get; }
        public nint Length { get; }
        public int Height { get; }
        public int Width { get; }
        public Span2D<T> Span { get; }
        public Memory2D<T> this[Range rows, Range columns] { get; }

        public Memory2D<T> Slice(int row, int column, int height, int width);
        public void CopyTo(Memory<T> destination);
        public bool TryCopyTo(Memory<T> destination);
        public void CopyTo(Memory2D<T> destination);
        public bool TryCopyTo(Memory2D<T> destination);
        public MemoryHandle Pin();
        public bool TryGetMemory(out Memory<T> memory);
        public T[,] ToArray();
        public override bool Equals(object? obj);
        public bool Equals(Memory2D<T> other);
        public override int GetHashCode();
        public override string ToString();

        public static implicit operator Memory2D<T>(T[,]? array) => new Memory2D<T>(array);
    }

    public readonly struct ReadOnlyMemory2D<T> : IEquatable<ReadOnlyMemory2D<T>>
    {
        public ReadOnlyMemory2D(string text, int height, int width);
        public ReadOnlyMemory2D(string text, int offset, int height, int width, int pitch);
        public ReadOnlyMemory2D(T[] array, int height, int width);
        public ReadOnlyMemory2D(T[] array, int offset, int height, int width, int pitch);
        public ReadOnlyMemory2D(T[,]? array);
        public ReadOnlyMemory2D(T[,]? array, int row, int column, int height, int width);
        public ReadOnlyMemory2D(T[,,] array, int depth);
        public ReadOnlyMemory2D(T[,,] array, int depth, int row, int column, int height, int width);
        public ReadOnlyMemory2D(MemoryManager<T> memoryManager, int height, int width);
        public ReadOnlyMemory2D(MemoryManager<T> memoryManager, int offset, int height, int width, int pitch);

        public static ReadOnlyMemory2D<T> DangerousCreate(object instance, ref T value, int height, int width, int pitch);

        public static ReadOnlyMemory2D<T> Empty  { get; }
        public bool IsEmpty { get; }
        public nint Length { get; }
        public int Height { get; }
        public int Width { get; }
        public ReadOnlySpan2D<T> Span { get; }
        public ReadOnlyMemory2D<T> this[Range rows, Range columns] { get; }

        public ReadOnlyMemory2D<T> Slice(int row, int column, int height, int width);
        public void CopyTo(Memory<T> destination);
        public bool TryCopyTo(Memory<T> destination);
        public void CopyTo(Memory2D<T> destination);
        public bool TryCopyTo(Memory2D<T> destination);
        public MemoryHandle Pin();
        public bool TryGetMemory(out ReadOnlyMemory<T> memory);
        public T[,] ToArray();
        public override bool Equals(object? obj);
        public bool Equals(ReadOnlyMemory2D<T> other);
        public override int GetHashCode();
        public override string ToString();

        public static implicit operator ReadOnlyMemory2D<T>(T[,]? array);
        public static implicit operator ReadOnlyMemory2D<T>(Memory2D<T> memory);
    }

    public readonly ref struct Span2D<T>
    {
        public Span2D(void* pointer, int height, int width, int pitch);
        public Span2D(T[] array, int height, int width);
        public Span2D(T[] array, int offset, int height, int width, int pitch);
        public Span2D(T[,]? array);
        public Span2D(T[,]? array, int row, int column, int height, int width);
        public Span2D(T[,,] array, int depth);
        public Span2D(T[,,] array, int depth, int row, int column, int height, int width);

        public static Span2D<T> DangerousCreate(ref T value, int height, int width, int pitch);

        public static Span2D<T> Empty { get; }
        public bool IsEmpty { get; }
        public nint Length { get; }
        public int Height { get; }
        public int Width { get; }
        public ref T this[int row, int column] { get; }
        public ref T this[Index row, Index column] { get; }
        public Span2D<T> this[Range rows, Range columns] { get; }

        public void Clear();
        public void CopyTo(Span<T> destination);
        public void CopyTo(Span2D<T> destination);
        public bool TryCopyTo(Span<T> destination);
        public bool TryCopyTo(Span2D<T> destination);
        public void Fill(T value);
        public ref T GetPinnableReference();
        public ref T DangerousGetReference();
        public ref T DangerousGetReferenceAt(int i, int j);
        public Span2D<T> Slice(int row, int column, int height, int width);
        public Span<T> GetRowSpan(int row);
        public bool TryGetSpan(out Span<T> span);
        public T[,] ToArray();
        public override bool Equals(object? obj);
        public override int GetHashCode();
        public override string ToString();

        public static bool operator ==(Span2D<T> left, Span2D<T> right);
        public static bool operator !=(Span2D<T> left, Span2D<T> right);
        public static implicit operator Span2D<T>(T[,]? array);

        public RefEnumerable<T> GetRow(int row);
        public RefEnumerable<T> GetColumn(int column);
        public Enumerator GetEnumerator();

        public ref struct Enumerator
        {
            public bool MoveNext();
            public readonly ref T Current { get; }
        }
    }

    public readonly ref struct ReadOnlySpan2D<T>
    {
        public ReadOnlySpan2D(void* pointer, int height, int width, int pitch);
        public ReadOnlySpan2D(T[] array, int height, int width);
        public ReadOnlySpan2D(T[] array, int offset, int height, int width, int pitch);
        public ReadOnlySpan2D(T[,]? array);
        public ReadOnlySpan2D(T[,]? array, int row, int column, int height, int width);
        public ReadOnlySpan2D(T[,,] array, int depth);
        public ReadOnlySpan2D(T[,,] array, int depth, int row, int column, int height, int width);

        public static ReadOnlySpan2D<T> DangerousCreate(in T value, int height, int width, int pitch);

        public static ReadOnlySpan2D<T> Empty { get; }
        public bool IsEmpty { get; }
        public nint Length { get; }
        public int Height { get; }
        public int Width { get; }
        public ref readonly T this[int row, int column] { get; }
        public ref readonly T this[Index row, Index column] { get; }
        public ReadOnlySpan2D<T> this[Range rows, Range columns] { get; }

        public void CopyTo(Span<T> destination);
        public void CopyTo(Span2D<T> destination);
        public bool TryCopyTo(Span<T> destination);
        public bool TryCopyTo(Span2D<T> destination);
        public ref T GetPinnableReference();
        public ref T DangerousGetReference();
        public ref T DangerousGetReferenceAt(int i, int j);
        public ReadOnlySpan2D<T> Slice(int row, int column, int width, int height);
        public ReadOnlySpan<T> GetRowSpan(int row);
        public bool TryGetSpan(out ReadOnlySpan<T> span);
        public T[,] ToArray();
        public override bool Equals(object? obj);
        public override int GetHashCode();
        public override string ToString();

        public static bool operator ==(ReadOnlySpan2D<T> left, ReadOnlySpan2D<T> right);
        public static bool operator !=(ReadOnlySpan2D<T> left, ReadOnlySpan2D<T> right);
        public static implicit operator ReadOnlySpan2D<T>(T[,]? array);
        public static implicit operator ReadOnlySpan2D<T>(Span2D<T> span);

        public ReadOnlyRefEnumerable<T> GetRow(int row);
        public ReadOnlyRefEnumerable<T> GetColumn(int column);
        public Enumerator GetEnumerator();

        public ref struct Enumerator
        {
            public bool MoveNext();
            public readonly ref readonly T Current { get; }
        }
    }
}

These two types are also mirrored as ReadOnlyMemory2D<T> and ReadOnlySpan2D<T>.

PR Checklist

Please check if your PR fulfills the following requirements:

  • Tested code with current supported SDKs
  • Pull Request has been submitted to the documentation repository instructions. Link: #376
  • Sample in sample app has been added / updated (for bug fixes / features)
  • Tests for the changes have been added (for bug fixes / features) (if applicable)
  • Header has been added to all new source files (run build/UpdateHeaders.bat)
  • Contains NO breaking changes

Additional notes

The breaking change is the removal of the Array2DRowEnumerable<T> and Array2DColumnEnumerable<T> types, which were used in the GetRow and GetColumn APIs. This approach had two problems:

  • There were two completely different iterator types, one per API
  • The GetRow API was a breaking change when upgrading, as the return type was just Span<T> there.

The solution, as discussed with @john-h-k and @tannergooding, was to introduce a new, shared RefEnumerable<T> type that can be used to traverse both rows and columns, and to use that one for both APIs. Next to them, a new API GetRowSpan was also added only on supported runtimes (so not a breaking change), which is equivalent to GetRow but returning a Span<T> value.

@msftbot
Copy link
Contributor

@msftbot msftbot bot commented Jun 17, 2020

Thanks Sergio0694 for opening a Pull Request! The reviewers will test the PR and highlight if there is any conflict or changes required. If the PR is approved we will proceed to merge the pull request 🙌

@Sergio0694 Sergio0694 marked this pull request as ready for review Jun 19, 2020
@msftbot
Copy link
Contributor

@msftbot msftbot bot commented Jun 19, 2020

This PR has been marked as "needs attention 👋" and awaiting a response from the team.

Sergio0694 added a commit to Sergio0694/WindowsCommunityToolkit that referenced this pull request Jul 27, 2020
This reverts commit 78a0266. Done to avoid conflicts, since these two enumerable types are already being overhauled in windows-toolkit#3353 anyway.
Copy link
Member

@michael-hawker michael-hawker left a comment

Going to wait until #3351 gets merged.

Copy link

@HurricanKai HurricanKai left a comment

I looked at most of this code, and while I'm not 100% sure, I think it all looks good to me.
I've got 2 usability issues though:

  • why are the width/height indexes called i, j? why not x, y?
  • Why do Equals & GetHashCode just throw?
Microsoft.Toolkit.HighPerformance/Memory/Span2D{T}.cs Outdated Show resolved Hide resolved
@Sergio0694
Copy link
Member Author

@Sergio0694 Sergio0694 commented Sep 10, 2020

Hey @HurricanKai, thanks for the review! 😊

As for your questions:

  • I called them i and j mostly out of habit, I suppose it would make sense to call them y and x, yes (not x and y though, the first order is the vertical coordinate)
  • Equals and GetHashCode throw to mirror the behavior of the Span<T> types in the BCL, which do the same (you can see the code in CoreCLR here). Like with those types, we're throwing here and instead implementing the == and != operators. I believe the intent here is that == is just used to indicate whether two memory regions map to the same area, while using GetHashCode and Equals could be confusing since they would not actually check the contents, but just the internal reference. So two different areas with the same contents would not be reported as being the same. Granted, that's the same for eg. arrays, but... I'm really just following their example here to make things consistent with the BCL 😄
@Sergio0694 Sergio0694 mentioned this pull request Sep 24, 2020
4 of 7 tasks complete
@michael-hawker
Copy link
Member

@michael-hawker michael-hawker commented Sep 25, 2020

@Sergio0694 this has some conflicts to resolve now?

@msftbot msftbot bot removed the needs attention 👋 label Sep 25, 2020
@Sergio0694
Copy link
Member Author

@Sergio0694 Sergio0694 commented Sep 25, 2020

@michael-hawker Yup, was just waiting on #3378 to be merged first since that'll edit some of the same files anyway, so that here I'll just have to resolve conflicts once when pulling changes from master from both the two other PRs for this package 😄

@michael-hawker
Copy link
Member

@michael-hawker michael-hawker commented Oct 8, 2020

@Sergio0694 #3378 is merged now, can you resolve conflicts and clean-up this PR so we can re-review and get it merged as well? Thanks! 🙂

Sergio0694 and others added 9 commits Oct 31, 2020
Copy link
Member

@michael-hawker michael-hawker left a comment

Few small comments on the unit tests, looking good!

@@ -23,12 +23,7 @@ public static class HashCodeExtensions
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Add<T>(ref this HashCode hashCode, ReadOnlySpan<T> span)
#if SPAN_RUNTIME_SUPPORT

This comment has been minimized.

@michael-hawker

michael-hawker Nov 3, 2020
Member

This has changed now I guess from the runtime side?

This comment has been minimized.

@Sergio0694

Sergio0694 Nov 3, 2020
Author Member

I've removed that restriction because I've now added polyfills for the RuntimeHelpers.IsReferenceOrContainsReferences<T>() API on .NET Standard 2.0 and below (I needed this for other code in the Memory2D<T> and Span2D<T> types, so I used it here too while I was at it), I use that here:

// 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.
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())

Now that older targets can test this too at runtime, we no longer have to add that extra type constraint, which was basically just a precaution due to the lack of this API at runtime. You can see how we switch the polyfill on here:

#if SPAN_RUNTIME_SUPPORT
using RuntimeHelpers = System.Runtime.CompilerServices.RuntimeHelpers;
#else
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#endif

So now on all targets the API will just automatically pick the right implementation for each input type T 😄


Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(-1));

Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(20));

This comment has been minimized.

@michael-hawker

michael-hawker Nov 3, 2020
Member

We should also be testing the 'off-by-1' index of '3', eh? (here and where-ever)

This comment has been minimized.

@Sergio0694

Sergio0694 Nov 3, 2020
Author Member

Right! I had added those tests when getting rows/columns from spans, but not from 2D arrays directly!
Fixed in 7a65a29, for both GetRow and GetColumn in this file 😄


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

This comment has been minimized.

@michael-hawker

michael-hawker Nov 3, 2020
Member

We're not checking here that no mutation occurred, eh? As the line 281 above would have copied the same values? (I mean we know it failed as '20' is still in the 3rd row, but may be better to use a different column like '3' here to be safe to guard against this case too.)

This comment has been minimized.

@Sergio0694

Sergio0694 Nov 3, 2020
Author Member

Good catch! Also added one extra check for the entire target array for good measure 😄
Done in 6bb9e5a, including the same changes for the comment below.

@@ -135,8 +135,7 @@ public static partial class ParallelHelper
/// Processes the batch of actions at a specified index
/// </summary>
/// <param name="i">The index of the batch to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]

This comment has been minimized.

@michael-hawker

michael-hawker Nov 3, 2020
Member

Expected for the inlining to disappear?

This comment has been minimized.

@Sergio0694

Sergio0694 Nov 3, 2020
Author Member

Yeah this was just a leftover, it's not there in the other implementations either. This method is just called once per thread anyway and does all the job. It's the actual delegates within each invocation of this method that need to be inlined, as they're invoked in a tight loop, not this one. As in, this loop here:

for (int j = low; j < end; j++)
{
ref TItem rj = ref Unsafe.Add(ref r0, (nint)(uint)j);
Unsafe.AsRef(this.action).Invoke(ref rj);
}

// 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

This comment has been minimized.

@michael-hawker

michael-hawker Nov 3, 2020
Member

So if this is native to the platform, how do we unit test the code for both possible sizes?

@@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFrameworks>netstandard1.4;netstandard2.0;netstandard2.1;netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>

This comment has been minimized.

@michael-hawker

michael-hawker Nov 3, 2020
Member

Does this need anything new for our build pipeline/tooling requirements for folks?

This comment has been minimized.

@Sergio0694

Sergio0694 Nov 3, 2020
Author Member

Commenting here about the same comments from the stream, for future reference. Right now this works on the latest versions of VS stable (not the preview preview), because it uses the same Roslyn codebase as VS Preview, just from an older branch. As such, some C# 9 features are buggy/outdated (eg. function pointers), but since here we're really only using the new nint type, that bit in particular mostly works. I only had to add an extra explicit cast here to ensure the correct codegen due to a small compiler bug that's fixed in VS Preview, for instance:

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

As in, this PR should work fine when compiled with VS stable, but especially due to all the other C# 9 changes coming in other PRs, as well as the .NET 5 support itself, we should definitely update the CI build of VS to the new release as soon as it drops hopefully next week when .NET 5 is announced at .NET Conf 😄


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

This comment has been minimized.

@michael-hawker

michael-hawker Nov 3, 2020
Member

Should we check the intermediary results? I've noticed these tests are chaining quite a few operations together, so it may be better to help find problems in a particular part if we're validating the first part before moving on? (Or split them as separate tests?)

This comment has been minimized.

@Sergio0694

Sergio0694 Nov 3, 2020
Author Member

I think I addressed this in 6bb9e5a, let me know if that's alright 😄


// A couple of tests for invalid parameters, ie. layers out of range
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 20));

This comment has been minimized.

@michael-hawker

michael-hawker Nov 3, 2020
Member

same here for off-by-one

This comment has been minimized.

@Sergio0694

Sergio0694 Nov 3, 2020
Author Member

Added a few new tests for this and ReadOnlyMemory2D<T> versions in 7b04c7d 👍

@michael-hawker michael-hawker added this to the 7.0 milestone Nov 3, 2020
@michael-hawker michael-hawker added this to In progress in Features 7.0 via automation Nov 3, 2020
@msftbot
Copy link
Contributor

@msftbot msftbot bot commented Nov 3, 2020

Hello @michael-hawker!

Because this pull request has the auto merge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.
Copy link
Contributor

@RosarioPulella RosarioPulella left a comment

Other than cleaning up some no longer needed SuppressMessageAttributes, It looks good.

[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")]
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")]
Comment on lines 242 to 243

This comment has been minimized.

@RosarioPulella

RosarioPulella Nov 5, 2020
Contributor

There a few SuppressMessageAttributes left on methods that are no longer needed. It would be good to remove them from methods that no longer need it.

This comment has been minimized.

@Sergio0694

Sergio0694 Nov 5, 2020
Author Member

Oh, good catch, forgot about those! 😄
Fixed in 7e67a9c, and also fixed some other leftover warnings in the tests.

Features 7.0 automation moved this from In progress to Reviewer approved Nov 5, 2020
@Sergio0694
Copy link
Member Author

@Sergio0694 Sergio0694 commented Nov 5, 2020

Removing the auto merge tag now that the PR is approved as I was waiting on one last piece of feedback from Tanner on a possible minor tweak to do to some constructors in the new types, so that if that's needed I could just do it here instead of opening a whole other PR just to fix that afterwards? cc. @michael-hawker

@michael-hawker michael-hawker merged commit fdee563 into windows-toolkit:master Nov 5, 2020
4 of 5 checks passed
4 of 5 checks passed
Toolkit Releases Toolkit Releases failed
Details
Toolkit-CI Build #7.0.0-build.460+f52535187b succeeded
Details
WIP Ready for review
Details
auto-merge.config.enforce No dynamic merge policies are applicable.
license/cla All CLA requirements met.
Details
Features 7.0 automation moved this from Reviewer approved to Done Nov 5, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Features 7.0
  
Done
Linked issues

Successfully merging this pull request may close these issues.

None yet

6 participants
You can’t perform that action at this time.