Optimize Span.Copy and Span.TryCopyTo (#15947)
[platform/upstream/coreclr.git] / src / mscorlib / shared / System / ReadOnlySpan.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     /// ReadOnlySpan 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 ReadOnlySpan<T>
29     {
30         /// <summary>A byref or a native ptr.</summary>
31         internal readonly ByReference<T> _pointer;
32         /// <summary>The number of elements this ReadOnlySpan contains.</summary>
33 #if PROJECTN
34         [Bound]
35 #endif
36         private readonly int _length;
37
38         /// <summary>
39         /// Creates a new read-only 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         [MethodImpl(MethodImplOptions.AggressiveInlining)]
45         public ReadOnlySpan(T[] array)
46         {
47             if (array == null)
48                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
49
50             _pointer = new ByReference<T>(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()));
51             _length = array.Length;
52         }
53
54         /// <summary>
55         /// Creates a new read-only span over the portion of the target array beginning
56         /// at 'start' index and ending at 'end' index (exclusive).
57         /// </summary>
58         /// <param name="array">The target array.</param>
59         /// <param name="start">The index at which to begin the read-only span.</param>
60         /// <param name="length">The number of items in the read-only span.</param>
61         /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="array"/> is a null
62         /// reference (Nothing in Visual Basic).</exception>
63         /// <exception cref="System.ArgumentOutOfRangeException">
64         /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=Length).
65         /// </exception>
66         [MethodImpl(MethodImplOptions.AggressiveInlining)]
67         public ReadOnlySpan(T[] array, int start, int length)
68         {
69             if (array == null)
70                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
71             if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
72                 ThrowHelper.ThrowArgumentOutOfRangeException();
73
74             _pointer = new ByReference<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start));
75             _length = length;
76         }
77
78         /// <summary>
79         /// Creates a new read-only span over the target unmanaged buffer.  Clearly this
80         /// is quite dangerous, because we are creating arbitrarily typed T's
81         /// out of a void*-typed block of memory.  And the length is not checked.
82         /// But if this creation is correct, then all subsequent uses are correct.
83         /// </summary>
84         /// <param name="pointer">An unmanaged pointer to memory.</param>
85         /// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param>
86         /// <exception cref="System.ArgumentException">
87         /// Thrown when <typeparamref name="T"/> is reference type or contains pointers and hence cannot be stored in unmanaged memory.
88         /// </exception>
89         /// <exception cref="System.ArgumentOutOfRangeException">
90         /// Thrown when the specified <paramref name="length"/> is negative.
91         /// </exception>
92         [CLSCompliant(false)]
93         [MethodImpl(MethodImplOptions.AggressiveInlining)]
94         public unsafe ReadOnlySpan(void* pointer, int length)
95         {
96             if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
97                 ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
98             if (length < 0)
99                 ThrowHelper.ThrowArgumentOutOfRangeException();
100
101             _pointer = new ByReference<T>(ref Unsafe.As<byte, T>(ref *(byte*)pointer));
102             _length = length;
103         }
104
105         /// <summary>
106         /// Create a new read-only span over a portion of a regular managed object. This can be useful
107         /// if part of a managed object represents a "fixed array." This is dangerous because neither the
108         /// <paramref name="length"/> is checked, nor <paramref name="obj"/> being null, nor the fact that
109         /// "rawPointer" actually lies within <paramref name="obj"/>.
110         /// </summary>
111         /// <param name="obj">The managed object that contains the data to span over.</param>
112         /// <param name="objectData">A reference to data within that object.</param>
113         /// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param>
114         [MethodImpl(MethodImplOptions.AggressiveInlining)]
115         [EditorBrowsable(EditorBrowsableState.Never)]
116         public static ReadOnlySpan<T> DangerousCreate(object obj, ref T objectData, int length) => new ReadOnlySpan<T>(ref objectData, length);
117
118         // Constructor for internal use only.
119         [MethodImpl(MethodImplOptions.AggressiveInlining)]
120         internal ReadOnlySpan(ref T ptr, int length)
121         {
122             Debug.Assert(length >= 0);
123
124             _pointer = new ByReference<T>(ref ptr);
125             _length = length;
126         }
127
128         //Debugger Display = {T[length]}
129         private string DebuggerDisplay => string.Format("{{{0}[{1}]}}", typeof(T).Name, _length);
130
131         /// <summary>
132         /// 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
133         /// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
134         /// </summary>
135         [MethodImpl(MethodImplOptions.AggressiveInlining)]
136         [EditorBrowsable(EditorBrowsableState.Never)]
137         internal ref readonly T DangerousGetPinnableReference()
138         {
139             return ref _pointer.Value;
140         }
141
142         /// <summary>
143         /// The number of items in the read-only span.
144         /// </summary>
145         public int Length
146         {
147             [NonVersionable]
148             get
149             {
150                 return _length;
151             }
152         }
153
154         /// <summary>
155         /// Returns true if Length is 0.
156         /// </summary>
157         public bool IsEmpty
158         {
159             [NonVersionable]
160             get
161             {
162                 return _length == 0;
163             }
164         }
165
166         /// <summary>
167         /// Returns the specified element of the read-only span.
168         /// </summary>
169         /// <param name="index"></param>
170         /// <returns></returns>
171         /// <exception cref="System.IndexOutOfRangeException">
172         /// Thrown when index less than 0 or index greater than or equal to Length
173         /// </exception>
174         public ref readonly T this[int index]
175         {
176 #if PROJECTN
177             [BoundsChecking]
178             get
179             {
180                 return ref Unsafe.Add(ref _pointer.Value, index);
181             }
182 #else
183             [Intrinsic]
184             [MethodImpl(MethodImplOptions.AggressiveInlining)]
185             [NonVersionable]
186             get
187             {
188                 if ((uint)index >= (uint)_length)
189                     ThrowHelper.ThrowIndexOutOfRangeException();
190                 return ref Unsafe.Add(ref _pointer.Value, index);
191             }
192 #endif
193         }
194
195         /// <summary>
196         /// Copies the contents of this read-only span into destination span. If the source
197         /// and destinations overlap, this method behaves as if the original values in
198         /// a temporary location before the destination is overwritten.
199         ///
200         /// <param name="destination">The span to copy items into.</param>
201         /// <exception cref="System.ArgumentException">
202         /// Thrown when the destination Span is shorter than the source Span.
203         /// </exception>
204         /// </summary>
205         public void CopyTo(Span<T> destination)
206         {
207             // Using "if (!TryCopyTo(...))" results in two branches: one for the length
208             // check, and one for the result of TryCopyTo. Since these checks are equivalent,
209             // we can optimize by performing the check once ourselves then calling Memmove directly.
210
211             if ((uint)_length <= (uint)destination.Length)
212             {
213                 Buffer.Memmove(ref destination.DangerousGetPinnableReference(), ref _pointer.Value, (nuint)_length);
214             }
215             else
216             {
217                 ThrowHelper.ThrowArgumentException_DestinationTooShort();
218             }
219         }
220
221         /// Copies the contents of this read-only span into destination span. If the source
222         /// and destinations overlap, this method behaves as if the original values in
223         /// a temporary location before the destination is overwritten.
224         /// </summary>
225         /// <returns>If the destination span is shorter than the source span, this method
226         /// return false and no data is written to the destination.</returns>
227         /// <param name="destination">The span to copy items into.</param>
228         public bool TryCopyTo(Span<T> destination)
229         {
230             bool retVal = false;
231             if ((uint)_length <= (uint)destination.Length)
232             {
233                 Buffer.Memmove(ref destination.DangerousGetPinnableReference(), ref _pointer.Value, (nuint)_length);
234                 retVal = true;
235             }
236             return retVal;
237         }
238
239         /// <summary>
240         /// Returns true if left and right point at the same memory and have the same length.  Note that
241         /// this does *not* check to see if the *contents* are equal.
242         /// </summary>
243         public static bool operator ==(ReadOnlySpan<T> left, ReadOnlySpan<T> right)
244         {
245             return left._length == right._length && Unsafe.AreSame<T>(ref left._pointer.Value, ref right._pointer.Value);
246         }
247
248         /// <summary>
249         /// Returns false if left and right point at the same memory and have the same length.  Note that
250         /// this does *not* check to see if the *contents* are equal.
251         /// </summary>
252         public static bool operator !=(ReadOnlySpan<T> left, ReadOnlySpan<T> right) => !(left == right);
253
254         /// <summary>
255         /// This method is not supported as spans cannot be boxed. To compare two spans, use operator==.
256         /// <exception cref="System.NotSupportedException">
257         /// Always thrown by this method.
258         /// </exception>
259         /// </summary>
260         [Obsolete("Equals() on ReadOnlySpan will always throw an exception. Use == instead.")]
261         [EditorBrowsable(EditorBrowsableState.Never)]
262         public override bool Equals(object obj)
263         {
264             throw new NotSupportedException(SR.NotSupported_CannotCallEqualsOnSpan);
265         }
266
267         /// <summary>
268         /// This method is not supported as spans cannot be boxed.
269         /// <exception cref="System.NotSupportedException">
270         /// Always thrown by this method.
271         /// </exception>
272         /// </summary>
273         [Obsolete("GetHashCode() on ReadOnlySpan will always throw an exception.")]
274         [EditorBrowsable(EditorBrowsableState.Never)]
275         public override int GetHashCode()
276         {
277             throw new NotSupportedException(SR.NotSupported_CannotCallGetHashCodeOnSpan);
278         }
279
280         /// <summary>
281         /// Returns a <see cref="String"/> with the name of the type and the number of elements
282         /// </summary>
283         public override string ToString() => "System.Span[" + Length.ToString() + "]";
284
285         /// <summary>
286         /// Defines an implicit conversion of an array to a <see cref="ReadOnlySpan{T}"/>
287         /// </summary>
288         public static implicit operator ReadOnlySpan<T>(T[] array) => array != null ? new ReadOnlySpan<T>(array) : default;
289
290         /// <summary>
291         /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="ReadOnlySpan{T}"/>
292         /// </summary>
293         public static implicit operator ReadOnlySpan<T>(ArraySegment<T> arraySegment)
294             => arraySegment.Array != null ? new ReadOnlySpan<T>(arraySegment.Array, arraySegment.Offset, arraySegment.Count) : default;
295
296         /// <summary>
297         /// Forms a slice out of the given read-only span, beginning at 'start'.
298         /// </summary>
299         /// <param name="start">The index at which to begin this slice.</param>
300         /// <exception cref="System.ArgumentOutOfRangeException">
301         /// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;=Length).
302         /// </exception>
303         [MethodImpl(MethodImplOptions.AggressiveInlining)]
304         public ReadOnlySpan<T> Slice(int start)
305         {
306             if ((uint)start > (uint)_length)
307                 ThrowHelper.ThrowArgumentOutOfRangeException();
308
309             return new ReadOnlySpan<T>(ref Unsafe.Add(ref _pointer.Value, start), _length - start);
310         }
311
312         /// <summary>
313         /// Forms a slice out of the given read-only span, beginning at 'start', of given length
314         /// </summary>
315         /// <param name="start">The index at which to begin this slice.</param>
316         /// <param name="length">The desired length for the slice (exclusive).</param>
317         /// <exception cref="System.ArgumentOutOfRangeException">
318         /// Thrown when the specified <paramref name="start"/> or end index is not in range (&lt;0 or &gt;=Length).
319         /// </exception>
320         [MethodImpl(MethodImplOptions.AggressiveInlining)]
321         public ReadOnlySpan<T> Slice(int start, int length)
322         {
323             if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
324                 ThrowHelper.ThrowArgumentOutOfRangeException();
325
326             return new ReadOnlySpan<T>(ref Unsafe.Add(ref _pointer.Value, start), length);
327         }
328
329         /// <summary>
330         /// Copies the contents of this read-only span into a new array.  This heap
331         /// allocates, so should generally be avoided, however it is sometimes
332         /// necessary to bridge the gap with APIs written in terms of arrays.
333         /// </summary>
334         public T[] ToArray()
335         {
336             if (_length == 0)
337                 return Array.Empty<T>();
338
339             var destination = new T[_length];
340             Buffer.Memmove(ref Unsafe.As<byte, T>(ref destination.GetRawSzArrayData()), ref _pointer.Value, (nuint)_length);
341             return destination;
342         }
343
344         /// <summary>
345         /// Returns a 0-length read-only span whose base is the null pointer.
346         /// </summary>
347         public static ReadOnlySpan<T> Empty => default(ReadOnlySpan<T>);
348
349         /// <summary>Gets an enumerator for this span.</summary>
350         public Enumerator GetEnumerator() => new Enumerator(this);
351
352         /// <summary>Enumerates the elements of a <see cref="ReadOnlySpan{T}"/>.</summary>
353         public ref struct Enumerator
354         {
355             /// <summary>The span being enumerated.</summary>
356             private readonly ReadOnlySpan<T> _span;
357             /// <summary>The next index to yield.</summary>
358             private int _index;
359
360             /// <summary>Initialize the enumerator.</summary>
361             /// <param name="span">The span to enumerate.</param>
362             [MethodImpl(MethodImplOptions.AggressiveInlining)]
363             internal Enumerator(ReadOnlySpan<T> span)
364             {
365                 _span = span;
366                 _index = -1;
367             }
368
369             /// <summary>Advances the enumerator to the next element of the span.</summary>
370             [MethodImpl(MethodImplOptions.AggressiveInlining)]
371             public bool MoveNext()
372             {
373                 int index = _index + 1;
374                 if (index < _span.Length)
375                 {
376                     _index = index;
377                     return true;
378                 }
379
380                 return false;
381             }
382
383             /// <summary>Gets the element at the current position of the enumerator.</summary>
384             public ref readonly T Current
385             {
386                 [MethodImpl(MethodImplOptions.AggressiveInlining)]
387                 get => ref _span[_index];
388             }
389         }
390     }
391 }