Improve RuntimeHelpers.GetSubArray (dotnet/coreclr#23829)
authorStephen Toub <stoub@microsoft.com>
Tue, 9 Apr 2019 11:21:40 +0000 (07:21 -0400)
committerGitHub <noreply@github.com>
Tue, 9 Apr 2019 11:21:40 +0000 (07:21 -0400)
* Improve RuntimeHelpers.GetSubArray

This change does three things.

First, it fixes `GetSubArray` to work when the supplied array is actually a `U[]` where `U : T`.  Currently this case ends up throwing an exception inside of span, which doesn't like working with arrays covariantly.

Second, it fixes argument validation so that we throw an ArgumentNullException if the input array is null rather than NullReferenceException.

Third, it improves the performance of `GetSubArray` for the 95% common case where either `T` is a value type or the type of the array matches the `T` type specified.

* Only use `Array.Empty<T>` when `typeof(T[]) == array.GetType()`

Commit migrated from https://github.com/dotnet/coreclr/commit/5fd24c69f75a1222a4d9b7abaed1c5712d47bd06

src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs

index fcb69ec..8dea0ae 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Runtime.Serialization;
+using Internal.Runtime.CompilerServices;
 
 namespace System.Runtime.CompilerServices
 {
@@ -13,22 +14,39 @@ namespace System.Runtime.CompilerServices
         public delegate void CleanupCode(object userData, bool exceptionThrown);
 
         /// <summary>
-        /// GetSubArray helper method for the compiler to slice an array using a range.
+        /// Slices the specified array using the specified range.
         /// </summary>
         public static T[] GetSubArray<T>(T[] array, Range range)
         {
-            Type elementType = array.GetType().GetElementType();
-            Span<T> source = array.AsSpan(range);
+            if (array == null)
+            {
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+            }
+
+            Range.OffsetAndLength offLen = range.GetOffsetAndLength(array.Length);
 
-            if (elementType.IsValueType)
+            if (default(T) != null || typeof(T[]) == array.GetType())
             {
-                return source.ToArray();
+                // We know the type of the array to be exactly T[].
+
+                if (offLen.Length == 0)
+                {
+                    return Array.Empty<T>();
+                }
+
+                var dest = new T[offLen.Length];
+                Buffer.Memmove(
+                    ref Unsafe.As<byte, T>(ref dest.GetRawSzArrayData()),
+                    ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), offLen.Offset),
+                    (uint)offLen.Length);
+                return dest;
             }
             else
             {
-                T[] newArray = (T[])Array.CreateInstance(elementType, source.Length);
-                source.CopyTo(newArray);
-                return newArray;
+                // The array is actually a U[] where U:T.
+                T[] dest = (T[])Array.CreateInstance(array.GetType().GetElementType(), offLen.Length);
+                Array.Copy(array, offLen.Offset, dest, 0, offLen.Length);
+                return dest;
             }
         }
 
@@ -63,4 +81,4 @@ namespace System.Runtime.CompilerServices
         {
         }
     }
-}
\ No newline at end of file
+}