Skip to content

Commit

Permalink
Bring back old array enumerator code (#88371)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
MichalStrehovsky committed Jul 7, 2023
1 parent 6085dc2 commit 4285e43
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,8 @@ internal IEnumerator<T> GetEnumerator<T>()
// ! 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1099,7 +1099,10 @@ private Array() { }
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,72 +69,60 @@ public void Reset()

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];
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/mono/System.Private.CoreLib/src/System/Array.Mono.cs
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,8 @@ internal bool InternalArray__ICollection_get_IsReadOnly()

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()
Expand Down

0 comments on commit 4285e43

Please sign in to comment.