Bring back old array enumerator code (#88371)
authorMichal Strehovský <MichalStrehovsky@users.noreply.github.com>
Fri, 7 Jul 2023 07:38:43 +0000 (16:38 +0900)
committerGitHub <noreply@github.com>
Fri, 7 Jul 2023 07:38:43 +0000 (16:38 +0900)
This is a 0.3% size saving for Stage1. We not only allow array enumerators to be preinitialized again, but also avoid introducing many array `MethodTables` (looks like the new enumerators force array MethodTables for cases where we could have avoided them).

Fixes #82993.

src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs
src/libraries/System.Private.CoreLib/src/System/Array.Enumerators.cs
src/mono/System.Private.CoreLib/src/System/Array.Mono.cs

index 8a32e8f..699f99c 100644 (file)
@@ -400,7 +400,8 @@ namespace System
             // ! Warning: "this" is an array, not an SZArrayHelper. See comments above
             // ! or you may introduce a security hole!
             T[] @this = Unsafe.As<T[]>(this);
-            return @this.Length == 0 ? SZGenericArrayEnumerator<T>.Empty : new SZGenericArrayEnumerator<T>(@this);
+            int length = @this.Length;
+            return length == 0 ? SZGenericArrayEnumerator<T>.Empty : new SZGenericArrayEnumerator<T>(@this, length);
         }
 
         private void CopyTo<T>(T[] array, int index)
index f2d1aac..1cb5604 100644 (file)
@@ -1099,7 +1099,10 @@ namespace System
         public new IEnumerator<T> GetEnumerator()
         {
             T[] @this = Unsafe.As<T[]>(this);
-            return @this.Length == 0 ? SZGenericArrayEnumerator<T>.Empty : new SZGenericArrayEnumerator<T>(@this);
+            // get length so we don't have to call the Length property again in ArrayEnumerator constructor
+            // and avoid more checking there too.
+            int length = @this.Length;
+            return length == 0 ? SZGenericArrayEnumerator<T>.Empty : new SZGenericArrayEnumerator<T>(@this, length);
         }
 
         public int Count
index 90542e9..60aa839 100644 (file)
@@ -69,72 +69,60 @@ namespace System
 
     internal abstract class SZGenericArrayEnumeratorBase : IDisposable
     {
-        protected readonly Array _array;
         protected int _index;
+        protected readonly int _endIndex;
 
-        protected SZGenericArrayEnumeratorBase(Array array)
+        protected SZGenericArrayEnumeratorBase(int endIndex)
         {
-            Debug.Assert(array != null);
-
-            _array = array;
             _index = -1;
+            _endIndex = endIndex;
         }
 
         public bool MoveNext()
         {
             int index = _index + 1;
-            uint length = (uint)_array.NativeLength;
-            if ((uint)index >= length)
+            if ((uint)index < (uint)_endIndex)
             {
-                _index = (int)length;
-                return false;
+                _index = index;
+                return true;
             }
-            _index = index;
-            return true;
+            _index = _endIndex;
+            return false;
         }
 
         public void Reset() => _index = -1;
 
-#pragma warning disable CA1822 // https://github.com/dotnet/roslyn-analyzers/issues/5911
         public void Dispose()
         {
         }
-#pragma warning restore CA1822
     }
 
     internal sealed class SZGenericArrayEnumerator<T> : SZGenericArrayEnumeratorBase, IEnumerator<T>
     {
+        private readonly T[]? _array;
+
         /// <summary>Provides an empty enumerator singleton.</summary>
         /// <remarks>
         /// If the consumer is using SZGenericArrayEnumerator elsewhere or is otherwise likely
         /// to be using T[] elsewhere, this singleton should be used.  Otherwise, GenericEmptyEnumerator's
         /// singleton should be used instead, as it doesn't reference T[] in order to reduce footprint.
         /// </remarks>
-#pragma warning disable CA1825
-        internal static readonly SZGenericArrayEnumerator<T> Empty =
-            // Array.Empty is intentionally omitted here, since we don't want to pay for generic instantiations
-            // that wouldn't have otherwise been used.
-            new SZGenericArrayEnumerator<T>(new T[0]);
-#pragma warning restore CA1825
-
-        public SZGenericArrayEnumerator(T[] array)
-            : base(array)
+        internal static readonly SZGenericArrayEnumerator<T> Empty = new SZGenericArrayEnumerator<T>(null, 0);
+
+        internal SZGenericArrayEnumerator(T[]? array, int endIndex)
+            : base(endIndex)
         {
+            Debug.Assert(array == null || endIndex == array.Length);
+            _array = array;
         }
 
         public T Current
         {
             get
             {
-                int index = _index;
-                T[] array = Unsafe.As<T[]>(_array);
-
-                if ((uint)index >= (uint)array.Length)
-                {
-                    ThrowHelper.ThrowInvalidOperationException_EnumCurrent(index);
-                }
-
-                return array[index];
+                if ((uint)_index >= (uint)_endIndex)
+                    ThrowHelper.ThrowInvalidOperationException_EnumCurrent(_index);
+                return _array![_index];
             }
         }
 
index af3e7f0..f6782a4 100644 (file)
@@ -468,7 +468,8 @@ namespace System
 
         internal IEnumerator<T> InternalArray__IEnumerable_GetEnumerator<T>()
         {
-            return Length == 0 ? SZGenericArrayEnumerator<T>.Empty : new SZGenericArrayEnumerator<T>(Unsafe.As<T[]>(this));
+            int length = Length;
+            return length == 0 ? SZGenericArrayEnumerator<T>.Empty : new SZGenericArrayEnumerator<T>(Unsafe.As<T[]>(this), length);
         }
 
         internal void InternalArray__ICollection_Clear()