Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit a68803c

Browse files
tarekghjkotas
authored andcommitted
Span factoring (#26667)
1 parent c727d12 commit a68803c

File tree

8 files changed

+638
-795
lines changed

8 files changed

+638
-795
lines changed
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
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 partial 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+
// Constructor for internal use only.
106+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
107+
internal ReadOnlySpan(ref T ptr, int length)
108+
{
109+
Debug.Assert(length >= 0);
110+
111+
_pointer = new ByReference<T>(ref ptr);
112+
_length = length;
113+
}
114+
115+
/// <summary>
116+
/// Returns the specified element of the read-only span.
117+
/// </summary>
118+
/// <param name="index"></param>
119+
/// <returns></returns>
120+
/// <exception cref="System.IndexOutOfRangeException">
121+
/// Thrown when index less than 0 or index greater than or equal to Length
122+
/// </exception>
123+
public ref readonly T this[int index]
124+
{
125+
#if PROJECTN
126+
[BoundsChecking]
127+
get
128+
{
129+
return ref Unsafe.Add(ref _pointer.Value, index);
130+
}
131+
#else
132+
[Intrinsic]
133+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
134+
[NonVersionable]
135+
get
136+
{
137+
if ((uint)index >= (uint)_length)
138+
ThrowHelper.ThrowIndexOutOfRangeException();
139+
return ref Unsafe.Add(ref _pointer.Value, index);
140+
}
141+
#endif
142+
}
143+
144+
/// <summary>
145+
/// Copies the contents of this read-only span into destination span. If the source
146+
/// and destinations overlap, this method behaves as if the original values in
147+
/// a temporary location before the destination is overwritten.
148+
///
149+
/// <param name="destination">The span to copy items into.</param>
150+
/// <exception cref="System.ArgumentException">
151+
/// Thrown when the destination Span is shorter than the source Span.
152+
/// </exception>
153+
/// </summary>
154+
public void CopyTo(Span<T> destination)
155+
{
156+
// Using "if (!TryCopyTo(...))" results in two branches: one for the length
157+
// check, and one for the result of TryCopyTo. Since these checks are equivalent,
158+
// we can optimize by performing the check once ourselves then calling Memmove directly.
159+
160+
if ((uint)_length <= (uint)destination.Length)
161+
{
162+
Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length);
163+
}
164+
else
165+
{
166+
ThrowHelper.ThrowArgumentException_DestinationTooShort();
167+
}
168+
}
169+
170+
/// Copies the contents of this read-only span into destination span. If the source
171+
/// and destinations overlap, this method behaves as if the original values in
172+
/// a temporary location before the destination is overwritten.
173+
/// </summary>
174+
/// <returns>If the destination span is shorter than the source span, this method
175+
/// return false and no data is written to the destination.</returns>
176+
/// <param name="destination">The span to copy items into.</param>
177+
public bool TryCopyTo(Span<T> destination)
178+
{
179+
bool retVal = false;
180+
if ((uint)_length <= (uint)destination.Length)
181+
{
182+
Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length);
183+
retVal = true;
184+
}
185+
return retVal;
186+
}
187+
188+
/// <summary>
189+
/// Returns true if left and right point at the same memory and have the same length. Note that
190+
/// this does *not* check to see if the *contents* are equal.
191+
/// </summary>
192+
public static bool operator ==(ReadOnlySpan<T> left, ReadOnlySpan<T> right)
193+
{
194+
return left._length == right._length && Unsafe.AreSame<T>(ref left._pointer.Value, ref right._pointer.Value);
195+
}
196+
197+
/// <summary>
198+
/// For <see cref="ReadOnlySpan{Char}"/>, returns a new instance of string that represents the characters pointed to by the span.
199+
/// Otherwise, returns a <see cref="String"/> with the name of the type and the number of elements.
200+
/// </summary>
201+
public override string ToString()
202+
{
203+
if (typeof(T) == typeof(char))
204+
{
205+
unsafe
206+
{
207+
fixed (char* src = &Unsafe.As<T, char>(ref _pointer.Value))
208+
return new string(src, 0, _length);
209+
}
210+
}
211+
return string.Format("System.ReadOnlySpan<{0}>[{1}]", typeof(T).Name, _length);
212+
}
213+
214+
/// <summary>
215+
/// Forms a slice out of the given read-only span, beginning at 'start'.
216+
/// </summary>
217+
/// <param name="start">The index at which to begin this slice.</param>
218+
/// <exception cref="System.ArgumentOutOfRangeException">
219+
/// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;=Length).
220+
/// </exception>
221+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
222+
public ReadOnlySpan<T> Slice(int start)
223+
{
224+
if ((uint)start > (uint)_length)
225+
ThrowHelper.ThrowArgumentOutOfRangeException();
226+
227+
return new ReadOnlySpan<T>(ref Unsafe.Add(ref _pointer.Value, start), _length - start);
228+
}
229+
230+
/// <summary>
231+
/// Forms a slice out of the given read-only span, beginning at 'start', of given length
232+
/// </summary>
233+
/// <param name="start">The index at which to begin this slice.</param>
234+
/// <param name="length">The desired length for the slice (exclusive).</param>
235+
/// <exception cref="System.ArgumentOutOfRangeException">
236+
/// Thrown when the specified <paramref name="start"/> or end index is not in range (&lt;0 or &gt;=Length).
237+
/// </exception>
238+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
239+
public ReadOnlySpan<T> Slice(int start, int length)
240+
{
241+
if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
242+
ThrowHelper.ThrowArgumentOutOfRangeException();
243+
244+
return new ReadOnlySpan<T>(ref Unsafe.Add(ref _pointer.Value, start), length);
245+
}
246+
247+
/// <summary>
248+
/// Copies the contents of this read-only span into a new array. This heap
249+
/// allocates, so should generally be avoided, however it is sometimes
250+
/// necessary to bridge the gap with APIs written in terms of arrays.
251+
/// </summary>
252+
public T[] ToArray()
253+
{
254+
if (_length == 0)
255+
return Array.Empty<T>();
256+
257+
var destination = new T[_length];
258+
Buffer.Memmove(ref Unsafe.As<byte, T>(ref destination.GetRawSzArrayData()), ref _pointer.Value, (nuint)_length);
259+
return destination;
260+
}
261+
}
262+
}

0 commit comments

Comments
 (0)