From c43d0e22f7dfed57b85ed974bbe43997eeae35b9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 14:49:11 -0700 Subject: [PATCH] [release/9.0] Empty slice fix (#107880) * empty slice fix * PR comments and more testing * fixing check * fixing array rent init * fixes from PR comments --------- Co-authored-by: Michael Sharp --- .../Tensors/netcore/ReadOnlyTensorSpan.cs | 57 +++-- .../Tensors/netcore/TensorExtensions.cs | 213 +++++++++--------- .../Numerics/Tensors/netcore/TensorSpan.cs | 61 +++-- .../Tensors/netcore/TensorSpanHelpers.cs | 3 - .../tests/ReadOnlyTensorSpanTests.cs | 46 ++++ .../tests/TensorSpanTests.cs | 46 ++++ 6 files changed, 285 insertions(+), 141 deletions(-) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan.cs index b71e560bdd17d..80320bdccc80d 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using static System.Runtime.InteropServices.JavaScript.JSType; using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute; using EditorBrowsableState = System.ComponentModel.EditorBrowsableState; @@ -526,14 +525,14 @@ public void CopyTo(scoped TensorSpan destination) if (Rank > TensorShape.MaxInlineRank) { curIndexesArray = ArrayPool.Shared.Rent(Rank); - curIndexes = curIndexesArray; - curIndexes = curIndexes.Slice(0, Rank); + curIndexes = curIndexesArray.AsSpan(0, Rank); } else { curIndexesArray = null; curIndexes = stackalloc nint[Rank]; } + curIndexes.Clear(); nint copiedValues = 0; TensorSpan slice = destination.Slice(_shape.Lengths); @@ -573,14 +572,14 @@ public bool TryCopyTo(scoped TensorSpan destination) if (Rank > TensorShape.MaxInlineRank) { curIndexesArray = ArrayPool.Shared.Rent(Rank); - curIndexes = curIndexesArray; - curIndexes = curIndexes.Slice(0, Rank); + curIndexes = curIndexesArray.AsSpan(0, Rank); } else { curIndexesArray = null; curIndexes = stackalloc nint[Rank]; } + curIndexes.Clear(); nint copiedValues = 0; TensorSpan slice = destination.Slice(_shape.Lengths); @@ -654,34 +653,58 @@ public ReadOnlyTensorSpan Slice(params scoped ReadOnlySpan ranges) if (ranges.Length != Lengths.Length) throw new ArgumentOutOfRangeException(nameof(ranges), "Number of dimensions to slice does not equal the number of dimensions in the span"); + ReadOnlyTensorSpan toReturn; scoped Span lengths; scoped Span offsets; + nint[]? lengthsArray; + nint[]? offsetsArray; if (Rank > TensorShape.MaxInlineRank) { - lengths = stackalloc nint[Rank]; - offsets = stackalloc nint[Rank]; + lengthsArray = ArrayPool.Shared.Rent(Rank); + lengths = lengthsArray.AsSpan(0, Rank); + + offsetsArray = ArrayPool.Shared.Rent(Rank); + offsets = offsetsArray.AsSpan(0, Rank); } else { - lengths = new nint[Rank]; - offsets = new nint[Rank]; + lengths = stackalloc nint[Rank]; + offsets = stackalloc nint[Rank]; + + lengthsArray = null; + offsetsArray = null; } + lengths.Clear(); + offsets.Clear(); for (int i = 0; i < ranges.Length; i++) { (offsets[i], lengths[i]) = ranges[i].GetOffsetAndLength(Lengths[i]); } + // FlattenedLength is computed everytime so using a local to cache the value. + nint flattenedLength = FlattenedLength; nint index = 0; - for (int i = 0; i < offsets.Length; i++) + + if (flattenedLength != 0) { - index += Strides[i] * (offsets[i]); + for (int i = 0; i < offsets.Length; i++) + { + index += Strides[i] * (offsets[i]); + } } - if (index >= _shape._memoryLength || index < 0) + if ((index >= _shape._memoryLength || index < 0) && flattenedLength != 0) ThrowHelper.ThrowIndexOutOfRangeException(); - return new ReadOnlyTensorSpan(ref Unsafe.Add(ref _reference, index), lengths, _shape.Strides, _shape._memoryLength - index); + toReturn = new ReadOnlyTensorSpan(ref Unsafe.Add(ref _reference, index), lengths, _shape.Strides, _shape._memoryLength - index); + + if (offsetsArray != null) + ArrayPool.Shared.Return(offsetsArray); + if (lengthsArray != null) + ArrayPool.Shared.Return(lengthsArray); + + return toReturn; } /// @@ -698,14 +721,14 @@ public bool TryFlattenTo(scoped Span destination) if (Rank > TensorShape.MaxInlineRank) { curIndexesArray = ArrayPool.Shared.Rent(Rank); - curIndexes = curIndexesArray; - curIndexes = curIndexes.Slice(0, Rank); + curIndexes = curIndexesArray.AsSpan(0, Rank); } else { curIndexesArray = null; curIndexes = stackalloc nint[Rank]; } + curIndexes.Clear(); nint copiedValues = 0; while (copiedValues < _shape.FlattenedLength) @@ -739,14 +762,14 @@ public void FlattenTo(scoped Span destination) if (Rank > TensorShape.MaxInlineRank) { curIndexesArray = ArrayPool.Shared.Rent(Rank); - curIndexes = curIndexesArray; - curIndexes = curIndexes.Slice(0, Rank); + curIndexes = curIndexesArray.AsSpan(0, Rank); } else { curIndexesArray = null; curIndexes = stackalloc nint[Rank]; } + curIndexes.Clear(); nint copiedValues = 0; while (copiedValues < _shape.FlattenedLength) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs index 176a2b4ef5ec7..d470c7dbb40c8 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs @@ -398,14 +398,15 @@ public static ref readonly TensorSpan ConcatenateOnDimension(int dimension if (tensors[0].Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(tensors[0].Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, tensors[0].Rank); + curIndex = curIndexArray.AsSpan(0, tensors[0].Rank); } else { curIndexArray = null; curIndex = stackalloc nint[tensors[0].Rank]; } + curIndex.Clear(); + nint srcIndex; nint copyLength; @@ -503,14 +504,14 @@ public static ref readonly TensorSpan Equals(scoped in ReadOnlyTensorSp if (right.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(right.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, right.Rank); + curIndex = curIndexArray.AsSpan(0, right.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[right.Rank]; } + curIndex.Clear(); for (int i = 0; i < left.FlattenedLength; i++) { @@ -559,14 +560,14 @@ public static ref readonly TensorSpan Equals(scoped in ReadOnlyTensorSp if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -604,14 +605,14 @@ public static bool EqualsAll(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpa if (broadcastedLeft.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, broadcastedRight.Rank); + curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[broadcastedRight.Rank]; } + curIndex.Clear(); for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) { @@ -643,14 +644,14 @@ public static bool EqualsAll(in ReadOnlyTensorSpan x, T y) if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -688,14 +689,14 @@ public static bool EqualsAny(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpa if (broadcastedRight.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, broadcastedRight.Rank); + curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; } + curIndex.Clear(); for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) { @@ -727,14 +728,14 @@ public static bool EqualsAny(in ReadOnlyTensorSpan x, T y) if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -874,14 +875,14 @@ public static ref readonly TensorSpan GreaterThan(scoped in ReadOnlyTen if (right.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(right.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, right.Rank); + curIndex = curIndexArray.AsSpan(0, right.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[right.Rank]; } + curIndex.Clear(); for (int i = 0; i < left.FlattenedLength; i++) { @@ -934,14 +935,14 @@ public static ref readonly TensorSpan GreaterThan(scoped in ReadOnlyTen if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -994,14 +995,14 @@ public static ref readonly TensorSpan GreaterThan(T x, scoped in ReadOn if (y.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, y.Rank); + curIndex = curIndexArray.AsSpan(0, y.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[y.Rank]; } + curIndex.Clear(); for (int i = 0; i < y.FlattenedLength; i++) { @@ -1083,14 +1084,14 @@ public static ref readonly TensorSpan GreaterThanOrEqual(scoped in Read if (right.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(right.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, right.Rank); + curIndex = curIndexArray.AsSpan(0, right.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[right.Rank]; } + curIndex.Clear(); for (int i = 0; i < left.FlattenedLength; i++) { @@ -1143,14 +1144,14 @@ public static ref readonly TensorSpan GreaterThanOrEqual(scoped in Read if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -1203,14 +1204,14 @@ public static ref readonly TensorSpan GreaterThanOrEqual(T x, scoped in if (y.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, y.Rank); + curIndex = curIndexArray.AsSpan(0, y.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[y.Rank]; } + curIndex.Clear(); for (int i = 0; i < y.FlattenedLength; i++) { @@ -1247,14 +1248,14 @@ public static bool GreaterThanAny(in ReadOnlyTensorSpan x, in ReadOnlyTens if (broadcastedRight.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, broadcastedRight.Rank); + curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; } + curIndex.Clear(); for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) { @@ -1286,14 +1287,14 @@ public static bool GreaterThanAny(in ReadOnlyTensorSpan x, T y) if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -1325,14 +1326,14 @@ public static bool GreaterThanAny(T x, in ReadOnlyTensorSpan y) if (y.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, y.Rank); + curIndex = curIndexArray.AsSpan(0, y.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[y.Rank]; } + curIndex.Clear(); for (int i = 0; i < y.FlattenedLength; i++) { @@ -1370,14 +1371,14 @@ public static bool GreaterThanOrEqualAny(in ReadOnlyTensorSpan x, in ReadO if (broadcastedRight.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, broadcastedRight.Rank); + curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; } + curIndex.Clear(); for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) { @@ -1409,14 +1410,14 @@ public static bool GreaterThanOrEqualAny(in ReadOnlyTensorSpan x, T y) if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -1448,14 +1449,14 @@ public static bool GreaterThanOrEqualAny(T x, in ReadOnlyTensorSpan y) if (y.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, y.Rank); + curIndex = curIndexArray.AsSpan(0, y.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[y.Rank]; } + curIndex.Clear(); for (int i = 0; i < y.FlattenedLength; i++) { @@ -1494,14 +1495,14 @@ public static bool GreaterThanAll(in ReadOnlyTensorSpan x, in ReadOnlyTens if (broadcastedLeft.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, broadcastedRight.Rank); + curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[broadcastedRight.Rank]; } + curIndex.Clear(); for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) { @@ -1533,14 +1534,14 @@ public static bool GreaterThanAll(in ReadOnlyTensorSpan x, T y) if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -1572,14 +1573,14 @@ public static bool GreaterThanAll(T x, in ReadOnlyTensorSpan y) if (y.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, y.Rank); + curIndex = curIndexArray.AsSpan(0, y.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[y.Rank]; } + curIndex.Clear(); for (int i = 0; i < y.FlattenedLength; i++) { @@ -1618,14 +1619,14 @@ public static bool GreaterThanOrEqualAll(in ReadOnlyTensorSpan x, in ReadO if (broadcastedLeft.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, broadcastedRight.Rank); + curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[broadcastedRight.Rank]; } + curIndex.Clear(); for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) { @@ -1657,14 +1658,14 @@ public static bool GreaterThanOrEqualAll(in ReadOnlyTensorSpan x, T y) if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -1696,14 +1697,14 @@ public static bool GreaterThanOrEqualAll(T x, in ReadOnlyTensorSpan y) if (y.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, y.Rank); + curIndex = curIndexArray.AsSpan(0, y.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[y.Rank]; } + curIndex.Clear(); for (int i = 0; i < y.FlattenedLength; i++) { @@ -1786,14 +1787,14 @@ public static ref readonly TensorSpan LessThan(scoped in ReadOnlyTensor if (right.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(right.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, right.Rank); + curIndex = curIndexArray.AsSpan(0, right.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[right.Rank]; } + curIndex.Clear(); for (int i = 0; i < left.FlattenedLength; i++) { @@ -1846,14 +1847,14 @@ public static ref readonly TensorSpan LessThan(scoped in ReadOnlyTensor if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -1906,14 +1907,14 @@ public static ref readonly TensorSpan LessThan(T x, scoped in ReadOnlyT if (y.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, y.Rank); + curIndex = curIndexArray.AsSpan(0, y.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[y.Rank]; } + curIndex.Clear(); for (int i = 0; i < y.FlattenedLength; i++) { @@ -1995,14 +1996,14 @@ public static ref readonly TensorSpan LessThanOrEqual(scoped in ReadOnl if (right.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(right.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, right.Rank); + curIndex = curIndexArray.AsSpan(0, right.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[right.Rank]; } + curIndex.Clear(); for (int i = 0; i < left.FlattenedLength; i++) { @@ -2055,14 +2056,14 @@ public static ref readonly TensorSpan LessThanOrEqual(scoped in ReadOnl if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -2115,14 +2116,14 @@ public static ref readonly TensorSpan LessThanOrEqual(T x, scoped in Re if (y.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, y.Rank); + curIndex = curIndexArray.AsSpan(0, y.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[y.Rank]; } + curIndex.Clear(); for (int i = 0; i < y.FlattenedLength; i++) { @@ -2160,14 +2161,14 @@ public static bool LessThanAny(in ReadOnlyTensorSpan x, in ReadOnlyTensorS if (broadcastedRight.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, broadcastedRight.Rank); + curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; } + curIndex.Clear(); for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) { @@ -2199,14 +2200,14 @@ public static bool LessThanAny(in ReadOnlyTensorSpan x, T y) if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -2238,14 +2239,14 @@ public static bool LessThanAny(T x, in ReadOnlyTensorSpan y) if (y.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, y.Rank); + curIndex = curIndexArray.AsSpan(0, y.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[y.Rank]; } + curIndex.Clear(); for (int i = 0; i < y.FlattenedLength; i++) { @@ -2284,14 +2285,14 @@ public static bool LessThanOrEqualAny(in ReadOnlyTensorSpan x, in ReadOnly if (broadcastedRight.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, broadcastedRight.Rank); + curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; } + curIndex.Clear(); for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) { @@ -2323,14 +2324,14 @@ public static bool LessThanOrEqualAny(in ReadOnlyTensorSpan x, T y) if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -2362,14 +2363,14 @@ public static bool LessThanOrEqualAny(T x, in ReadOnlyTensorSpan y) if (y.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, y.Rank); + curIndex = curIndexArray.AsSpan(0, y.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[y.Rank]; } + curIndex.Clear(); for (int i = 0; i <= y.FlattenedLength; i++) { @@ -2407,14 +2408,14 @@ public static bool LessThanAll(in ReadOnlyTensorSpan x, in ReadOnlyTensorS if (broadcastedRight.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, broadcastedRight.Rank); + curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; } + curIndex.Clear(); for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) { @@ -2446,14 +2447,14 @@ public static bool LessThanAll(in ReadOnlyTensorSpan x, T y) if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -2485,14 +2486,14 @@ public static bool LessThanAll(T x, in ReadOnlyTensorSpan y) if (y.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, y.Rank); + curIndex = curIndexArray.AsSpan(0, y.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[y.Rank]; } + curIndex.Clear(); for (int i = 0; i < y.FlattenedLength; i++) { @@ -2530,14 +2531,14 @@ public static bool LessThanOrEqualAll(in ReadOnlyTensorSpan x, in ReadOnly if (broadcastedRight.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, broadcastedRight.Rank); + curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; } + curIndex.Clear(); for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) { @@ -2569,14 +2570,14 @@ public static bool LessThanOrEqualAll(in ReadOnlyTensorSpan x, T y) if (x.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, x.Rank); + curIndex = curIndexArray.AsSpan(0, x.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[x.Rank]; } + curIndex.Clear(); for (int i = 0; i < x.FlattenedLength; i++) { @@ -2608,14 +2609,14 @@ public static bool LessThanOrEqualAll(T x, in ReadOnlyTensorSpan y) if (y.Rank > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, y.Rank); + curIndex = curIndexArray.AsSpan(0, y.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[y.Rank]; } + curIndex.Clear(); for (int i = 0; i < y.FlattenedLength; i++) { @@ -2680,9 +2681,12 @@ public static Tensor PermuteDimensions(this Tensor tensor, params ReadO if (outTensor.Rank > 6) { indicesArray = ArrayPool.Shared.Rent(outTensor.Rank); - indexes = indicesArray; + indexes = indicesArray.AsSpan(0, outTensor.Rank); + indexes.Clear(); + permutedIndicesArray = ArrayPool.Shared.Rent(outTensor.Rank); - permutedIndices = permutedIndicesArray; + permutedIndices = permutedIndicesArray.AsSpan(0, outTensor.Rank); + permutedIndices.Clear(); } else { @@ -2960,9 +2964,12 @@ public static ref readonly TensorSpan ReverseDimension(scoped in ReadOnlyT if (tensor.Rank > 6) { oIndicesArray = ArrayPool.Shared.Rent(tensor.Rank); - oIndices = oIndicesArray; + oIndices = oIndicesArray.AsSpan(0, tensor.Rank); + oIndices.Clear(); + iIndicesArray = ArrayPool.Shared.Rent(tensor.Rank); - iIndices = iIndicesArray; + iIndices = iIndicesArray.AsSpan(0, tensor.Rank); + iIndices.Clear(); } else { @@ -3094,9 +3101,12 @@ public static Tensor[] Split(scoped in ReadOnlyTensorSpan tensor, int s if (tensor.Rank > 6) { oIndicesArray = ArrayPool.Shared.Rent(tensor.Rank); - oIndices = oIndicesArray; + oIndices = oIndicesArray.AsSpan(0, tensor.Rank); + oIndices.Clear(); + iIndicesArray = ArrayPool.Shared.Rent(tensor.Rank); - iIndices = iIndicesArray; + iIndices = iIndicesArray.AsSpan(0, tensor.Rank); + iIndices.Clear(); } else { @@ -3430,7 +3440,8 @@ public static string ToString(this in ReadOnlyTensorSpan tensor, params Re if (tensor.Rank > 6) { curIndexesArray = ArrayPool.Shared.Rent(tensor.Rank); - curIndexes = curIndexesArray; + curIndexes = curIndexesArray.AsSpan(0, tensor.Rank); + curIndexes.Clear(); } else { @@ -6746,14 +6757,14 @@ private static ref readonly TensorSpan TensorPrimitivesHelperSpanInSpanOut if (input.Lengths.Length > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(input.Lengths.Length); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, input.Rank); + curIndex = curIndexArray.AsSpan(0, input.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[input.Lengths.Length]; } + curIndex.Clear(); int copiedValues = 0; nint rowLength = input.Lengths[^1]; @@ -6798,14 +6809,14 @@ private static ref readonly TensorSpan TensorPrimitivesHelperSpanInTInSpanOut if (input.Lengths.Length > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(input.Lengths.Length); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, input.Rank); + curIndex = curIndexArray.AsSpan(0, input.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[input.Lengths.Length]; } + curIndex.Clear(); int copiedValues = 0; nint rowLength = input.Lengths[^1]; @@ -6850,14 +6861,14 @@ private static ref readonly TensorSpan TensorPrimitivesHelperTInSpanInSpanOut if (input.Lengths.Length > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(input.Lengths.Length); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, input.Rank); + curIndex = curIndexArray.AsSpan(0, input.Rank); } else { curIndexArray = null; curIndex = stackalloc nint[input.Lengths.Length]; } + curIndex.Clear(); int copiedValues = 0; nint rowLength = input.Lengths[^1]; @@ -6914,14 +6925,14 @@ private static ref readonly TensorSpan TensorPrimitivesHelperTwoSpanInSpanOut if (newSize.Length > TensorShape.MaxInlineRank) { curIndexArray = ArrayPool.Shared.Rent(newSize.Length); - curIndex = curIndexArray; - curIndex = curIndex.Slice(0, newSize.Length); + curIndex = curIndexArray.AsSpan(0, newSize.Length); } else { curIndexArray = null; curIndex = stackalloc nint[newSize.Length]; } + curIndex.Clear(); int outputOffset = 0; // neither row contiguous diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs index 8bd8f989f3895..a4a641052cf59 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using static System.Runtime.InteropServices.JavaScript.JSType; using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute; using EditorBrowsableState = System.ComponentModel.EditorBrowsableState; @@ -510,14 +509,14 @@ public void Clear() if (Rank > TensorShape.MaxInlineRank) { curIndexesArray = ArrayPool.Shared.Rent(Rank); - curIndexes = curIndexesArray; - curIndexes = curIndexes.Slice(0, Rank); + curIndexes = curIndexesArray.AsSpan(0, Rank); } else { curIndexesArray = null; curIndexes = stackalloc nint[Rank]; } + curIndexes.Clear(); nint clearedValues = 0; while (clearedValues < _shape.FlattenedLength) @@ -560,14 +559,14 @@ public void CopyTo(scoped TensorSpan destination) if (Rank > TensorShape.MaxInlineRank) { curIndexesArray = ArrayPool.Shared.Rent(Rank); - curIndexes = curIndexesArray; - curIndexes = curIndexes.Slice(0, Rank); + curIndexes = curIndexesArray.AsSpan(0, Rank); } else { curIndexesArray = null; curIndexes = stackalloc nint[Rank]; } + curIndexes.Clear(); nint copiedValues = 0; TensorSpan slice = destination.Slice(_shape.Lengths); @@ -603,14 +602,14 @@ public bool TryCopyTo(scoped TensorSpan destination) if (Rank > TensorShape.MaxInlineRank) { curIndexesArray = ArrayPool.Shared.Rent(Rank); - curIndexes = curIndexesArray; - curIndexes = curIndexes.Slice(0, Rank); + curIndexes = curIndexesArray.AsSpan(0, Rank); } else { curIndexesArray = null; curIndexes = stackalloc nint[Rank]; } + curIndexes.Clear(); nint copiedValues = 0; TensorSpan slice = destination.Slice(_shape.Lengths); @@ -690,37 +689,59 @@ public TensorSpan Slice(params scoped ReadOnlySpan ranges) if (ranges.Length != Lengths.Length) ThrowHelper.ThrowIndexOutOfRangeException(); + TensorSpan toReturn; scoped Span lengths; scoped Span offsets; + nint[]? lengthsArray; + nint[]? offsetsArray; if (Rank > TensorShape.MaxInlineRank) { - lengths = stackalloc nint[Rank]; - offsets = stackalloc nint[Rank]; + lengthsArray = ArrayPool.Shared.Rent(Rank); + lengths = lengthsArray.AsSpan(0, Rank); + + offsetsArray = ArrayPool.Shared.Rent(Rank); + offsets = offsetsArray.AsSpan(0, Rank); } else { - lengths = new nint[Rank]; - offsets = new nint[Rank]; + lengths = stackalloc nint[Rank]; + offsets = stackalloc nint[Rank]; + + lengthsArray = null; + offsetsArray = null; } + lengths.Clear(); + offsets.Clear(); for (int i = 0; i < ranges.Length; i++) { (offsets[i], lengths[i]) = ranges[i].GetOffsetAndLength(Lengths[i]); } + // When we have an empty Tensor and someone wants to slice all of it, we should return an empty Tensor. + // FlattenedLength is computed everytime so using a local to cache the value. + nint flattenedLength = FlattenedLength; nint index = 0; - for (int i = 0; i < offsets.Length; i++) - { - if (offsets[i] < 0 || offsets[i] >= Lengths[i]) - ThrowHelper.ThrowIndexOutOfRangeException(); - index += Strides[i] * (offsets[i]); + if (flattenedLength != 0) + { + for (int i = 0; i < offsets.Length; i++) + { + index += Strides[i] * (offsets[i]); + } } - if (index >= _shape._memoryLength || index < 0) + if ((index >= _shape._memoryLength || index < 0) && flattenedLength != 0) ThrowHelper.ThrowIndexOutOfRangeException(); - return new TensorSpan(ref Unsafe.Add(ref _reference, index), lengths, _shape.Strides, _shape._memoryLength - index); + toReturn = new TensorSpan(ref Unsafe.Add(ref _reference, index), lengths, _shape.Strides, _shape._memoryLength - index); + + if (offsetsArray != null) + ArrayPool.Shared.Return(offsetsArray); + if (lengthsArray != null) + ArrayPool.Shared.Return(lengthsArray); + + return toReturn; } /// @@ -755,14 +776,14 @@ public void FlattenTo(scoped Span destination) if (Rank > TensorShape.MaxInlineRank) { curIndexesArray = ArrayPool.Shared.Rent(Rank); - curIndexes = curIndexesArray; - curIndexes = curIndexes.Slice(0, Rank); + curIndexes = curIndexesArray.AsSpan(0, Rank); } else { curIndexesArray = null; curIndexes = stackalloc nint[Rank]; } + curIndexes.Clear(); nint copiedValues = 0; while (copiedValues < _shape.FlattenedLength) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.cs index d84b1130c96c3..adb5811392234 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.cs @@ -2,11 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; -using System.Collections.Specialized; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; namespace System.Numerics.Tensors diff --git a/src/libraries/System.Numerics.Tensors/tests/ReadOnlyTensorSpanTests.cs b/src/libraries/System.Numerics.Tensors/tests/ReadOnlyTensorSpanTests.cs index a962c84ada24f..bc39d7ad3c5fc 100644 --- a/src/libraries/System.Numerics.Tensors/tests/ReadOnlyTensorSpanTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/ReadOnlyTensorSpanTests.cs @@ -985,6 +985,52 @@ public static void ReadOnlyTensorSpanTryCopyTest() [Fact] public static void ReadOnlyTensorSpanSliceTest() { + // Make sure slicing an empty TensorSpan works + TensorSpan emptyTensorSpan = new TensorSpan(Array.Empty()).Slice(new NRange[] { .. }); + Assert.Equal([0], emptyTensorSpan.Lengths); + Assert.Equal(1, emptyTensorSpan.Rank); + Assert.Equal(0, emptyTensorSpan.FlattenedLength); + + // Make sure slicing a multi-dimensional empty TensorSpan works + int[,] empty2dArray = new int[2, 0]; + emptyTensorSpan = new TensorSpan(empty2dArray); + TensorSpan slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { .., .. }); + Assert.Equal([2, 0], slicedEmptyTensorSpan.Lengths); + Assert.Equal(2, slicedEmptyTensorSpan.Rank); + Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + + slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { 0..1, .. }); + Assert.Equal([1, 0], slicedEmptyTensorSpan.Lengths); + Assert.Equal(2, slicedEmptyTensorSpan.Rank); + Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + + // Make sure slicing a multi-dimensional empty TensorSpan works + int[,,,] empty4dArray = new int[2, 5, 1, 0]; + emptyTensorSpan = new TensorSpan(empty4dArray); + slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { .., .., .., .. }); + Assert.Equal([2, 5, 1, 0], slicedEmptyTensorSpan.Lengths); + Assert.Equal(4, slicedEmptyTensorSpan.Rank); + Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + + emptyTensorSpan = new TensorSpan(empty4dArray); + slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { 0..1, .., .., .. }); + Assert.Equal([1, 5, 1, 0], slicedEmptyTensorSpan.Lengths); + Assert.Equal(4, slicedEmptyTensorSpan.Rank); + Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + + emptyTensorSpan = new TensorSpan(empty4dArray); + slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { 0..1, 2..3, .., .. }); + Assert.Equal([1, 1, 1, 0], slicedEmptyTensorSpan.Lengths); + Assert.Equal(4, slicedEmptyTensorSpan.Rank); + Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + + empty4dArray = new int[2, 0, 1, 5]; + emptyTensorSpan = new TensorSpan(empty4dArray); + slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { .., .., .., .. }); + Assert.Equal([2, 0, 1, 5], slicedEmptyTensorSpan.Lengths); + Assert.Equal(4, slicedEmptyTensorSpan.Rank); + Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] results = new int[9]; ReadOnlyTensorSpan spanInt = a.AsTensorSpan(3, 3); diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.cs index 644a3de429083..b7472d4e118fc 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.cs @@ -1710,6 +1710,52 @@ public static void TensorSpanTryCopyTest() [Fact] public static void TensorSpanSliceTest() { + // Make sure slicing an empty TensorSpan works + TensorSpan emptyTensorSpan = new TensorSpan(Array.Empty()).Slice(new NRange[] { .. }); + Assert.Equal([0], emptyTensorSpan.Lengths); + Assert.Equal(1, emptyTensorSpan.Rank); + Assert.Equal(0, emptyTensorSpan.FlattenedLength); + + // Make sure slicing a multi-dimensional empty TensorSpan works + int[,] empty2dArray = new int[2, 0]; + emptyTensorSpan = new TensorSpan(empty2dArray); + TensorSpan slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { .. , .. }); + Assert.Equal([2, 0], slicedEmptyTensorSpan.Lengths); + Assert.Equal(2, slicedEmptyTensorSpan.Rank); + Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + + slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { 0..1, .. }); + Assert.Equal([1, 0], slicedEmptyTensorSpan.Lengths); + Assert.Equal(2, slicedEmptyTensorSpan.Rank); + Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + + // Make sure slicing a multi-dimensional empty TensorSpan works + int[,,,] empty4dArray = new int[2, 5, 1, 0]; + emptyTensorSpan = new TensorSpan(empty4dArray); + slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { .., .., .., .. }); + Assert.Equal([2, 5, 1, 0], slicedEmptyTensorSpan.Lengths); + Assert.Equal(4, slicedEmptyTensorSpan.Rank); + Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + + emptyTensorSpan = new TensorSpan(empty4dArray); + slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { 0..1, .., .., .. }); + Assert.Equal([1, 5, 1, 0], slicedEmptyTensorSpan.Lengths); + Assert.Equal(4, slicedEmptyTensorSpan.Rank); + Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + + emptyTensorSpan = new TensorSpan(empty4dArray); + slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { 0..1, 2..3, .., .. }); + Assert.Equal([1, 1, 1, 0], slicedEmptyTensorSpan.Lengths); + Assert.Equal(4, slicedEmptyTensorSpan.Rank); + Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + + empty4dArray = new int[2, 0, 1, 5]; + emptyTensorSpan = new TensorSpan(empty4dArray); + slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { .., .., .., .. }); + Assert.Equal([2, 0, 1, 5], slicedEmptyTensorSpan.Lengths); + Assert.Equal(4, slicedEmptyTensorSpan.Rank); + Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] results = new int[9]; TensorSpan spanInt = a.AsTensorSpan(3, 3);