From 9b65c32b10aa7e70f86527ca57cf8a3b096d47ec Mon Sep 17 00:00:00 2001 From: Levi Broderick Date: Wed, 21 Apr 2021 12:02:34 -0700 Subject: [PATCH] Add internal Array.Clear method --- .../src/System/Array.CoreCLR.cs | 22 +++++++++++ .../src/System/Array.cs | 2 +- .../System/Buffers/ConfigurableArrayPool.cs | 2 +- .../TlsOverPerCoreLockedStacksArrayPool.cs | 4 +- .../System/Collections/Generic/Dictionary.cs | 2 +- .../src/System/Collections/Generic/HashSet.cs | 2 +- .../System.Runtime/tests/System/ArrayTests.cs | 37 +++++++++++++++---- .../src/System/Array.Mono.cs | 14 +++++++ 8 files changed, 72 insertions(+), 13 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs index 5aa5a6a0f0f69..100fafba5a95f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -266,6 +266,28 @@ public static void ConstrainedCopy(Array sourceArray, int sourceIndex, Array des Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: true); } + internal static unsafe void Clear(Array array) + { + if (array == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + MethodTable* pMT = RuntimeHelpers.GetMethodTable(array); + nuint totalByteLength = pMT->ComponentSize * array.NativeLength; + ref byte pStart = ref array.GetRawArrayData(); + + if (!pMT->ContainsGCPointers) + { + SpanHelpers.ClearWithoutReferences(ref pStart, totalByteLength); + } + else + { + Debug.Assert(totalByteLength % (nuint)sizeof(IntPtr) == 0); + SpanHelpers.ClearWithReferences(ref Unsafe.As(ref pStart), totalByteLength / (nuint)sizeof(IntPtr)); + } + + // GC.KeepAlive(array) not required. pMT kept alive via `pStart` + } + // Sets length elements in array to 0 (or null for Object arrays), starting // at index. // diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index d1ac28f30de52..4879ff45deca8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -355,7 +355,7 @@ bool IList.Contains(object? value) void IList.Clear() { - Array.Clear(this, this.GetLowerBound(0), this.Length); + Array.Clear(this); } int IList.IndexOf(object? value) diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/ConfigurableArrayPool.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/ConfigurableArrayPool.cs index b52a1fa665aa5..d4678fee3ef17 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/ConfigurableArrayPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/ConfigurableArrayPool.cs @@ -143,7 +143,7 @@ public override void Return(T[] array, bool clearArray = false) // Clear the array if the user requests if (clearArray) { - Array.Clear(array, 0, array.Length); + Array.Clear(array); } // Return the buffer to its bucket. In the future, we might consider having Return return false diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs index ef398a300dd6f..59cb6556ad7ad 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs @@ -173,7 +173,7 @@ public override void Return(T[] array, bool clearArray = false) // Clear the array if the user requests. if (clearArray) { - Array.Clear(array, 0, array.Length); + Array.Clear(array); } // Check to see if the buffer is the correct size for this bucket @@ -274,7 +274,7 @@ public bool Trim() foreach (KeyValuePair tlsBuckets in s_allTlsBuckets) { T[]?[] buckets = tlsBuckets.Key; - Array.Clear(buckets, 0, buckets.Length); + Array.Clear(buckets); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs index adb97baacb170..c08031b58d63d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs @@ -246,7 +246,7 @@ public void Clear() Debug.Assert(_buckets != null, "_buckets should be non-null"); Debug.Assert(_entries != null, "_entries should be non-null"); - Array.Clear(_buckets, 0, _buckets.Length); + Array.Clear(_buckets); _count = 0; _freeList = -1; diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs index bda3510446545..d06b27ea6f7d9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs @@ -189,7 +189,7 @@ public void Clear() Debug.Assert(_buckets != null, "_buckets should be non-null"); Debug.Assert(_entries != null, "_entries should be non-null"); - Array.Clear(_buckets, 0, _buckets.Length); + Array.Clear(_buckets); _count = 0; _freeList = -1; _freeCount = 0; diff --git a/src/libraries/System.Runtime/tests/System/ArrayTests.cs b/src/libraries/System.Runtime/tests/System/ArrayTests.cs index 9893cf2ffd501..bada013ea1b02 100644 --- a/src/libraries/System.Runtime/tests/System/ArrayTests.cs +++ b/src/libraries/System.Runtime/tests/System/ArrayTests.cs @@ -4655,18 +4655,41 @@ public static void Copy_LargeMultiDimensionalArray() return; } - try + short[,] a = AllocateLargeMDArray(2, 2_000_000_000); + a[0, 1] = 42; + Array.Copy(a, 1, a, Int32.MaxValue, 2); + Assert.Equal(42, a[1, Int32.MaxValue - 2_000_000_000]); + + Array.Clear(a, Int32.MaxValue - 1, 3); + Assert.Equal(0, a[1, Int32.MaxValue - 2_000_000_000]); + } + + [OuterLoop] // Allocates large array + [ConditionalFact] + public static void Clear_LargeMultiDimensionalArray() + { + // If this test is run in a 32-bit process, the large allocation will fail. + if (IntPtr.Size != sizeof(long)) { - short[,] a = new short[2, 2_000_000_000]; - a[0, 1] = 42; - Array.Copy(a, 1, a, Int32.MaxValue, 2); - Assert.Equal(42, a[1, Int32.MaxValue - 2_000_000_000]); + return; + } + + short[,] a = AllocateLargeMDArray(2, 2_000_000_000); + a[1, 1_999_999_999] = 0x1234; + + ((IList)a).Clear(); + Assert.Equal(0, a[1, 1_999_999_999]); + } - Array.Clear(a, Int32.MaxValue - 1, 3); - Assert.Equal(0, a[1, Int32.MaxValue - 2_000_000_000]); + private static short[,] AllocateLargeMDArray(int dim0Length, int dim1Length) + { + try + { + return new short[dim0Length, dim1Length]; } catch (OutOfMemoryException) { + // not a fatal error - we'll just skip the test in this case throw new SkipTestException("Unable to allocate enough memory"); } } diff --git a/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs index 8c49dc5f3eff9..75f3ecd703661 100644 --- a/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs @@ -62,6 +62,20 @@ public int Rank get => Rank; } + internal static unsafe void Clear(Array array) + { + if (array == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + ref byte ptr = ref array.GetRawSzArrayData(); + nuint byteLength = array.NativeLength * (nuint)(uint)array.GetElementSize() /* force zero-extension */; + + if (RuntimeHelpers.ObjectHasReferences(array)) + SpanHelpers.ClearWithReferences(ref Unsafe.As(ref ptr), byteLength / (uint)sizeof(IntPtr)); + else + SpanHelpers.ClearWithoutReferences(ref ptr, byteLength); + } + public static unsafe void Clear(Array array, int index, int length) { if (array == null)