Optimize Span.Copy and Span.TryCopyTo (#15947)
[platform/upstream/coreclr.git] / src / mscorlib / shared / System / Span.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 using System.ComponentModel;
6 using System.Diagnostics;
7 using System.Runtime.CompilerServices;
8 using System.Runtime.Versioning;
9 using Internal.Runtime.CompilerServices;
10
11 #pragma warning disable 0809  //warning CS0809: Obsolete member 'Span<T>.Equals(object)' overrides non-obsolete member 'object.Equals(object)'
12
13 #if BIT64
14 using nuint = System.UInt64;
15 #else
16 using nuint = System.UInt32;
17 #endif
18
19 namespace System
20 {
21     /// <summary>
22     /// Span represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed
23     /// or native memory, or to memory allocated on the stack. It is type- and memory-safe.
24     /// </summary>
25     [DebuggerTypeProxy(typeof(SpanDebugView<>))]
26     [DebuggerDisplay("{DebuggerDisplay,nq}")]
27     [NonVersionable]
28     public readonly ref struct Span<T>
29     {
30         /// <summary>A byref or a native ptr.</summary>
31         internal readonly ByReference<T> _pointer;
32         /// <summary>The number of elements this Span contains.</summary>
33 #if PROJECTN
34         [Bound]
35 #endif
36         private readonly int _length;
37
38         /// <summary>
39         /// Creates a new span over the entirety of the target array.
40         /// </summary>
41         /// <param name="array">The target array.</param>
42         /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="array"/> is a null
43         /// reference (Nothing in Visual Basic).</exception>
44         /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
45         [MethodImpl(MethodImplOptions.AggressiveInlining)]
46         public Span(T[] array)
47         {
48             if (array == null)
49                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
50             if (default(T) == null && array.GetType() != typeof(T[]))
51                 ThrowHelper.ThrowArrayTypeMismatchException();
52
53             _pointer = new ByReference<T>(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()));
54             _length = array.Length;
55         }
56
57         /// <summary>
58         /// Creates a new span over the portion of the target array beginning
59         /// at 'start' index and ending at 'end' index (exclusive).
60         /// </summary>
61         /// <param name="array">The target array.</param>
62         /// <param name="start">The index at which to begin the span.</param>
63         /// <param name="length">The number of items in the span.</param>
64         /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="array"/> is a null
65         /// reference (Nothing in Visual Basic).</exception>
66         /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
67         /// <exception cref="System.ArgumentOutOfRangeException">
68         /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=Length).
69         /// </exception>
70         [MethodImpl(MethodImplOptions.AggressiveInlining)]
71         public Span(T[] array, int start, int length)
72         {
73             if (array == null)
74                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
75             if (default(T) == null && array.GetType() != typeof(T[]))
76                 ThrowHelper.ThrowArrayTypeMismatchException();
77             if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
78                 ThrowHelper.ThrowArgumentOutOfRangeException();
79
80             _pointer = new ByReference<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start));
81             _length = length;
82         }
83
84         /// <summary>
85         /// Creates a new span over the target unmanaged buffer.  Clearly this
86         /// is quite dangerous, because we are creating arbitrarily typed T's
87         /// out of a void*-typed block of memory.  And the length is not checked.
88         /// But if this creation is correct, then all subsequent uses are correct.
89         /// </summary>
90         /// <param name="pointer">An unmanaged pointer to memory.</param>
91         /// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param>
92         /// <exception cref="System.ArgumentException">
93         /// Thrown when <typeparamref name="T"/> is reference type or contains pointers and hence cannot be stored in unmanaged memory.
94         /// </exception>
95         /// <exception cref="System.ArgumentOutOfRangeException">
96         /// Thrown when the specified <paramref name="length"/> is negative.
97         /// </exception>
98         [CLSCompliant(false)]
99         [MethodImpl(MethodImplOptions.AggressiveInlining)]
100         public unsafe Span(void* pointer, int length)
101         {
102             if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
103                 ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
104             if (length < 0)
105                 ThrowHelper.ThrowArgumentOutOfRangeException();
106
107             _pointer = new ByReference<T>(ref Unsafe.As<byte, T>(ref *(byte*)pointer));
108             _length = length;
109         }
110
111         /// <summary>
112         /// Create a new span over a portion of a regular managed object. This can be useful
113         /// if part of a managed object represents a "fixed array." This is dangerous because neither the
114         /// <paramref name="length"/> is checked, nor <paramref name="obj"/> being null, nor the fact that
115         /// "rawPointer" actually lies within <paramref name="obj"/>.
116         /// </summary>
117         /// <param name="obj">The managed object that contains the data to span over.</param>
118         /// <param name="objectData">A reference to data within that object.</param>
119         /// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param>
120         [MethodImpl(MethodImplOptions.AggressiveInlining)]
121         [EditorBrowsable(EditorBrowsableState.Never)]
122         public static Span<T> DangerousCreate(object obj, ref T objectData, int length) => new Span<T>(ref objectData, length);
123
124         // Constructor for internal use only.
125         [MethodImpl(MethodImplOptions.AggressiveInlining)]
126         internal Span(ref T ptr, int length)
127         {
128             Debug.Assert(length >= 0);
129
130             _pointer = new ByReference<T>(ref ptr);
131             _length = length;
132         }
133
134         //Debugger Display = {T[length]}
135         private string DebuggerDisplay => string.Format("{{{0}[{1}]}}", typeof(T).Name, _length);
136
137         /// <summary>
138         /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element
139         /// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
140         /// </summary>
141         [MethodImpl(MethodImplOptions.AggressiveInlining)]
142         [EditorBrowsable(EditorBrowsableState.Never)]
143         internal ref T DangerousGetPinnableReference()
144         {
145             return ref _pointer.Value;
146         }
147
148         /// <summary>
149         /// The number of items in the span.
150         /// </summary>
151         public int Length
152         {
153             [NonVersionable]
154             get
155             {
156                 return _length;
157             }
158         }
159
160         /// <summary>
161         /// Returns true if Length is 0.
162         /// </summary>
163         public bool IsEmpty
164         {
165             [NonVersionable]
166             get
167             {
168                 return _length == 0;
169             }
170         }
171
172         /// <summary>
173         /// Returns a reference to specified element of the Span.
174         /// </summary>
175         /// <param name="index"></param>
176         /// <returns></returns>
177         /// <exception cref="System.IndexOutOfRangeException">
178         /// Thrown when index less than 0 or index greater than or equal to Length
179         /// </exception>
180         public ref T this[int index]
181         {
182 #if PROJECTN
183             [BoundsChecking]
184             get
185             {
186                 return ref Unsafe.Add(ref _pointer.Value, index);
187             }
188 #else
189             [Intrinsic]
190             [MethodImpl(MethodImplOptions.AggressiveInlining)]
191             [NonVersionable]
192             get
193             {
194                 if ((uint)index >= (uint)_length)
195                     ThrowHelper.ThrowIndexOutOfRangeException();
196                 return ref Unsafe.Add(ref _pointer.Value, index);
197             }
198 #endif
199         }
200
201         /// <summary>
202         /// Clears the contents of this span.
203         /// </summary>
204         [MethodImpl(MethodImplOptions.AggressiveInlining)]
205         public void Clear()
206         {
207             if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
208             {
209                 Span.ClearWithReferences(ref Unsafe.As<T, IntPtr>(ref _pointer.Value), (nuint)_length * (nuint)(Unsafe.SizeOf<T>() / sizeof(nuint)));
210             }
211             else
212             {
213                 Span.ClearWithoutReferences(ref Unsafe.As<T, byte>(ref _pointer.Value), (nuint)_length * (nuint)Unsafe.SizeOf<T>());
214             }
215         }
216
217         /// <summary>
218         /// Fills the contents of this span with the given value.
219         /// </summary>
220         public void Fill(T value)
221         {
222             if (Unsafe.SizeOf<T>() == 1)
223             {
224                 uint length = (uint)_length;
225                 if (length == 0)
226                     return;
227
228                 T tmp = value; // Avoid taking address of the "value" argument. It would regress performance of the loop below.
229                 Unsafe.InitBlockUnaligned(ref Unsafe.As<T, byte>(ref _pointer.Value), Unsafe.As<T, byte>(ref tmp), length);
230             }
231             else
232             {
233                 // Do all math as nuint to avoid unnecessary 64->32->64 bit integer truncations
234                 nuint length = (uint)_length;
235                 if (length == 0)
236                     return;
237
238                 ref T r = ref DangerousGetPinnableReference();
239
240                 // TODO: Create block fill for value types of power of two sizes e.g. 2,4,8,16
241
242                 nuint elementSize = (uint)Unsafe.SizeOf<T>();
243                 nuint i = 0;
244                 for (; i < (length & ~(nuint)7); i += 8)
245                 {
246                     Unsafe.AddByteOffset<T>(ref r, (i + 0) * elementSize) = value;
247                     Unsafe.AddByteOffset<T>(ref r, (i + 1) * elementSize) = value;
248                     Unsafe.AddByteOffset<T>(ref r, (i + 2) * elementSize) = value;
249                     Unsafe.AddByteOffset<T>(ref r, (i + 3) * elementSize) = value;
250                     Unsafe.AddByteOffset<T>(ref r, (i + 4) * elementSize) = value;
251                     Unsafe.AddByteOffset<T>(ref r, (i + 5) * elementSize) = value;
252                     Unsafe.AddByteOffset<T>(ref r, (i + 6) * elementSize) = value;
253                     Unsafe.AddByteOffset<T>(ref r, (i + 7) * elementSize) = value;
254                 }
255                 if (i < (length & ~(nuint)3))
256                 {
257                     Unsafe.AddByteOffset<T>(ref r, (i + 0) * elementSize) = value;
258                     Unsafe.AddByteOffset<T>(ref r, (i + 1) * elementSize) = value;
259                     Unsafe.AddByteOffset<T>(ref r, (i + 2) * elementSize) = value;
260                     Unsafe.AddByteOffset<T>(ref r, (i + 3) * elementSize) = value;
261                     i += 4;
262                 }
263                 for (; i < length; i++)
264                 {
265                     Unsafe.AddByteOffset<T>(ref r, i * elementSize) = value;
266                 }
267             }
268         }
269
270         /// <summary>
271         /// Copies the contents of this span into destination span. If the source
272         /// and destinations overlap, this method behaves as if the original values in
273         /// a temporary location before the destination is overwritten.
274         /// </summary>
275         /// <param name="destination">The span to copy items into.</param>
276         /// <exception cref="System.ArgumentException">
277         /// Thrown when the destination Span is shorter than the source Span.
278         /// </exception>
279         public void CopyTo(Span<T> destination)
280         {
281             // Using "if (!TryCopyTo(...))" results in two branches: one for the length
282             // check, and one for the result of TryCopyTo. Since these checks are equivalent,
283             // we can optimize by performing the check once ourselves then calling Memmove directly.
284
285             if ((uint)_length <= (uint)destination.Length)
286             {
287                 Buffer.Memmove(ref destination.DangerousGetPinnableReference(), ref _pointer.Value, (nuint)_length);
288             }
289             else
290             {
291                 ThrowHelper.ThrowArgumentException_DestinationTooShort();
292             }
293         }
294
295         /// <summary>
296         /// Copies the contents of this span into destination span. If the source
297         /// and destinations overlap, this method behaves as if the original values in
298         /// a temporary location before the destination is overwritten.
299         /// </summary>
300         /// <param name="destination">The span to copy items into.</param>
301         /// <returns>If the destination span is shorter than the source span, this method
302         /// return false and no data is written to the destination.</returns>        
303         public bool TryCopyTo(Span<T> destination)
304         {
305             bool retVal = false;
306             if ((uint)_length <= (uint)destination.Length)
307             {
308                 Buffer.Memmove(ref destination.DangerousGetPinnableReference(), ref _pointer.Value, (nuint)_length);
309                 retVal = true;
310             }
311             return retVal;
312         }
313
314         /// <summary>
315         /// Returns true if left and right point at the same memory and have the same length.  Note that
316         /// this does *not* check to see if the *contents* are equal.
317         /// </summary>
318         public static bool operator ==(Span<T> left, Span<T> right)
319         {
320             return left._length == right._length && Unsafe.AreSame<T>(ref left._pointer.Value, ref right._pointer.Value);
321         }
322
323         /// <summary>
324         /// Returns false if left and right point at the same memory and have the same length.  Note that
325         /// this does *not* check to see if the *contents* are equal.
326         /// </summary>
327         public static bool operator !=(Span<T> left, Span<T> right) => !(left == right);
328
329         /// <summary>
330         /// This method is not supported as spans cannot be boxed. To compare two spans, use operator==.
331         /// <exception cref="System.NotSupportedException">
332         /// Always thrown by this method.
333         /// </exception>
334         /// </summary>
335         [Obsolete("Equals() on Span will always throw an exception. Use == instead.")]
336         [EditorBrowsable(EditorBrowsableState.Never)]
337         public override bool Equals(object obj)
338         {
339             throw new NotSupportedException(SR.NotSupported_CannotCallEqualsOnSpan);
340         }
341
342         /// <summary>
343         /// This method is not supported as spans cannot be boxed.
344         /// <exception cref="System.NotSupportedException">
345         /// Always thrown by this method.
346         /// </exception>
347         /// </summary>
348         [Obsolete("GetHashCode() on Span will always throw an exception.")]
349         [EditorBrowsable(EditorBrowsableState.Never)]
350         public override int GetHashCode()
351         {
352             throw new NotSupportedException(SR.NotSupported_CannotCallGetHashCodeOnSpan);
353         }
354
355         /// <summary>
356         /// Returns a <see cref="String"/> with the name of the type and the number of elements
357         /// </summary>
358         public override string ToString() => "System.Span[" + Length.ToString() + "]";
359
360         /// <summary>
361         /// Defines an implicit conversion of an array to a <see cref="Span{T}"/>
362         /// </summary>
363         public static implicit operator Span<T>(T[] array) => array != null ? new Span<T>(array) : default;
364
365         /// <summary>
366         /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="Span{T}"/>
367         /// </summary>
368         public static implicit operator Span<T>(ArraySegment<T> arraySegment)
369             => arraySegment.Array != null ? new Span<T>(arraySegment.Array, arraySegment.Offset, arraySegment.Count) : default;
370
371         /// <summary>
372         /// Defines an implicit conversion of a <see cref="Span{T}"/> to a <see cref="ReadOnlySpan{T}"/>
373         /// </summary>
374         public static implicit operator ReadOnlySpan<T>(Span<T> span) => new ReadOnlySpan<T>(ref span._pointer.Value, span._length);
375
376         /// <summary>
377         /// Forms a slice out of the given span, beginning at 'start'.
378         /// </summary>
379         /// <param name="start">The index at which to begin this slice.</param>
380         /// <exception cref="System.ArgumentOutOfRangeException">
381         /// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;=Length).
382         /// </exception>
383         [MethodImpl(MethodImplOptions.AggressiveInlining)]
384         public Span<T> Slice(int start)
385         {
386             if ((uint)start > (uint)_length)
387                 ThrowHelper.ThrowArgumentOutOfRangeException();
388
389             return new Span<T>(ref Unsafe.Add(ref _pointer.Value, start), _length - start);
390         }
391
392         /// <summary>
393         /// Forms a slice out of the given span, beginning at 'start', of given length
394         /// </summary>
395         /// <param name="start">The index at which to begin this slice.</param>
396         /// <param name="length">The desired length for the slice (exclusive).</param>
397         /// <exception cref="System.ArgumentOutOfRangeException">
398         /// Thrown when the specified <paramref name="start"/> or end index is not in range (&lt;0 or &gt;=Length).
399         /// </exception>
400         [MethodImpl(MethodImplOptions.AggressiveInlining)]
401         public Span<T> Slice(int start, int length)
402         {
403             if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
404                 ThrowHelper.ThrowArgumentOutOfRangeException();
405
406             return new Span<T>(ref Unsafe.Add(ref _pointer.Value, start), length);
407         }
408
409         /// <summary>
410         /// Copies the contents of this span into a new array.  This heap
411         /// allocates, so should generally be avoided, however it is sometimes
412         /// necessary to bridge the gap with APIs written in terms of arrays.
413         /// </summary>
414         [MethodImpl(MethodImplOptions.AggressiveInlining)]
415         public T[] ToArray()
416         {
417             if (_length == 0)
418                 return Array.Empty<T>();
419
420             var destination = new T[_length];
421             Buffer.Memmove(ref Unsafe.As<byte, T>(ref destination.GetRawSzArrayData()), ref _pointer.Value, (nuint)_length);
422             return destination;
423         }
424
425         // <summary>
426         /// Returns an empty <see cref="Span{T}"/>
427         /// </summary>
428         public static Span<T> Empty => default(Span<T>);
429
430         /// <summary>Gets an enumerator for this span.</summary>
431         public Enumerator GetEnumerator() => new Enumerator(this);
432
433         /// <summary>Enumerates the elements of a <see cref="Span{T}"/>.</summary>
434         public ref struct Enumerator
435         {
436             /// <summary>The span being enumerated.</summary>
437             private readonly Span<T> _span;
438             /// <summary>The next index to yield.</summary>
439             private int _index;
440
441             /// <summary>Initialize the enumerator.</summary>
442             /// <param name="span">The span to enumerate.</param>
443             [MethodImpl(MethodImplOptions.AggressiveInlining)]
444             internal Enumerator(Span<T> span)
445             {
446                 _span = span;
447                 _index = -1;
448             }
449
450             /// <summary>Advances the enumerator to the next element of the span.</summary>
451             [MethodImpl(MethodImplOptions.AggressiveInlining)]
452             public bool MoveNext()
453             {
454                 int index = _index + 1;
455                 if (index < _span.Length)
456                 {
457                     _index = index;
458                     return true;
459                 }
460
461                 return false;
462             }
463
464             /// <summary>Gets the element at the current position of the enumerator.</summary>
465             public ref T Current
466             {
467                 [MethodImpl(MethodImplOptions.AggressiveInlining)]
468                 get => ref _span[_index];
469             }
470         }
471     }
472 }