From 639ef5dc0a263b410be880d1839e4684ce13f287 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 2 Feb 2018 11:40:39 -0800 Subject: [PATCH] Span factoring (dotnet/corefx#26667) Signed-off-by: dotnet-bot-corefx-mirror --- src/mscorlib/shared/System/ReadOnlySpan.Fast.cs | 262 ++++++++++++++++++ src/mscorlib/shared/System/ReadOnlySpan.cs | 259 +----------------- src/mscorlib/shared/System/Span.Fast.cs | 342 ++++++++++++++++++++++++ src/mscorlib/shared/System/Span.cs | 339 +---------------------- 4 files changed, 628 insertions(+), 574 deletions(-) create mode 100644 src/mscorlib/shared/System/ReadOnlySpan.Fast.cs create mode 100644 src/mscorlib/shared/System/Span.Fast.cs diff --git a/src/mscorlib/shared/System/ReadOnlySpan.Fast.cs b/src/mscorlib/shared/System/ReadOnlySpan.Fast.cs new file mode 100644 index 0000000..5c627cc --- /dev/null +++ b/src/mscorlib/shared/System/ReadOnlySpan.Fast.cs @@ -0,0 +1,262 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; +using Internal.Runtime.CompilerServices; + +#pragma warning disable 0809 //warning CS0809: Obsolete member 'Span.Equals(object)' overrides non-obsolete member 'object.Equals(object)' + +#if BIT64 +using nuint = System.UInt64; +#else +using nuint = System.UInt32; +#endif + +namespace System +{ + /// + /// ReadOnlySpan represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed + /// or native memory, or to memory allocated on the stack. It is type- and memory-safe. + /// + [DebuggerTypeProxy(typeof(SpanDebugView<>))] + [DebuggerDisplay("{DebuggerDisplay,nq}")] + [NonVersionable] + public readonly ref partial struct ReadOnlySpan + { + /// A byref or a native ptr. + internal readonly ByReference _pointer; + /// The number of elements this ReadOnlySpan contains. +#if PROJECTN + [Bound] +#endif + private readonly int _length; + + /// + /// Creates a new read-only span over the entirety of the target array. + /// + /// The target array. + /// Thrown when is a null + /// reference (Nothing in Visual Basic). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan(T[] array) + { + if (array == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + _pointer = new ByReference(ref Unsafe.As(ref array.GetRawSzArrayData())); + _length = array.Length; + } + + /// + /// Creates a new read-only span over the portion of the target array beginning + /// at 'start' index and ending at 'end' index (exclusive). + /// + /// The target array. + /// The index at which to begin the read-only span. + /// The number of items in the read-only span. + /// Thrown when is a null + /// reference (Nothing in Visual Basic). + /// + /// Thrown when the specified or end index is not in the range (<0 or >=Length). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan(T[] array, int start, int length) + { + if (array == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + _pointer = new ByReference(ref Unsafe.Add(ref Unsafe.As(ref array.GetRawSzArrayData()), start)); + _length = length; + } + + /// + /// Creates a new read-only span over the target unmanaged buffer. Clearly this + /// is quite dangerous, because we are creating arbitrarily typed T's + /// out of a void*-typed block of memory. And the length is not checked. + /// But if this creation is correct, then all subsequent uses are correct. + /// + /// An unmanaged pointer to memory. + /// The number of elements the memory contains. + /// + /// Thrown when is reference type or contains pointers and hence cannot be stored in unmanaged memory. + /// + /// + /// Thrown when the specified is negative. + /// + [CLSCompliant(false)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe ReadOnlySpan(void* pointer, int length) + { + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); + if (length < 0) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + _pointer = new ByReference(ref Unsafe.As(ref *(byte*)pointer)); + _length = length; + } + + // Constructor for internal use only. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan(ref T ptr, int length) + { + Debug.Assert(length >= 0); + + _pointer = new ByReference(ref ptr); + _length = length; + } + + /// + /// Returns the specified element of the read-only span. + /// + /// + /// + /// + /// Thrown when index less than 0 or index greater than or equal to Length + /// + public ref readonly T this[int index] + { +#if PROJECTN + [BoundsChecking] + get + { + return ref Unsafe.Add(ref _pointer.Value, index); + } +#else + [Intrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [NonVersionable] + get + { + if ((uint)index >= (uint)_length) + ThrowHelper.ThrowIndexOutOfRangeException(); + return ref Unsafe.Add(ref _pointer.Value, index); + } +#endif + } + + /// + /// Copies the contents of this read-only span into destination span. If the source + /// and destinations overlap, this method behaves as if the original values in + /// a temporary location before the destination is overwritten. + /// + /// The span to copy items into. + /// + /// Thrown when the destination Span is shorter than the source Span. + /// + /// + public void CopyTo(Span destination) + { + // Using "if (!TryCopyTo(...))" results in two branches: one for the length + // check, and one for the result of TryCopyTo. Since these checks are equivalent, + // we can optimize by performing the check once ourselves then calling Memmove directly. + + if ((uint)_length <= (uint)destination.Length) + { + Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length); + } + else + { + ThrowHelper.ThrowArgumentException_DestinationTooShort(); + } + } + + /// Copies the contents of this read-only span into destination span. If the source + /// and destinations overlap, this method behaves as if the original values in + /// a temporary location before the destination is overwritten. + /// + /// If the destination span is shorter than the source span, this method + /// return false and no data is written to the destination. + /// The span to copy items into. + public bool TryCopyTo(Span destination) + { + bool retVal = false; + if ((uint)_length <= (uint)destination.Length) + { + Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length); + retVal = true; + } + return retVal; + } + + /// + /// Returns true if left and right point at the same memory and have the same length. Note that + /// this does *not* check to see if the *contents* are equal. + /// + public static bool operator ==(ReadOnlySpan left, ReadOnlySpan right) + { + return left._length == right._length && Unsafe.AreSame(ref left._pointer.Value, ref right._pointer.Value); + } + + /// + /// For , returns a new instance of string that represents the characters pointed to by the span. + /// Otherwise, returns a with the name of the type and the number of elements. + /// + public override string ToString() + { + if (typeof(T) == typeof(char)) + { + unsafe + { + fixed (char* src = &Unsafe.As(ref _pointer.Value)) + return new string(src, 0, _length); + } + } + return string.Format("System.ReadOnlySpan<{0}>[{1}]", typeof(T).Name, _length); + } + + /// + /// Forms a slice out of the given read-only span, beginning at 'start'. + /// + /// The index at which to begin this slice. + /// + /// Thrown when the specified index is not in range (<0 or >=Length). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan Slice(int start) + { + if ((uint)start > (uint)_length) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + return new ReadOnlySpan(ref Unsafe.Add(ref _pointer.Value, start), _length - start); + } + + /// + /// Forms a slice out of the given read-only span, beginning at 'start', of given length + /// + /// The index at which to begin this slice. + /// The desired length for the slice (exclusive). + /// + /// Thrown when the specified or end index is not in range (<0 or >=Length). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan Slice(int start, int length) + { + if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + return new ReadOnlySpan(ref Unsafe.Add(ref _pointer.Value, start), length); + } + + /// + /// Copies the contents of this read-only span into a new array. This heap + /// allocates, so should generally be avoided, however it is sometimes + /// necessary to bridge the gap with APIs written in terms of arrays. + /// + public T[] ToArray() + { + if (_length == 0) + return Array.Empty(); + + var destination = new T[_length]; + Buffer.Memmove(ref Unsafe.As(ref destination.GetRawSzArrayData()), ref _pointer.Value, (nuint)_length); + return destination; + } + } +} diff --git a/src/mscorlib/shared/System/ReadOnlySpan.cs b/src/mscorlib/shared/System/ReadOnlySpan.cs index a7d1736..f0f0226 100644 --- a/src/mscorlib/shared/System/ReadOnlySpan.cs +++ b/src/mscorlib/shared/System/ReadOnlySpan.cs @@ -5,17 +5,12 @@ using System.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; +#if !FEATURE_PORTABLE_SPAN using System.Runtime.Versioning; -using Internal.Runtime.CompilerServices; +#endif // !FEATURE_PORTABLE_SPAN #pragma warning disable 0809 //warning CS0809: Obsolete member 'Span.Equals(object)' overrides non-obsolete member 'object.Equals(object)' -#if BIT64 -using nuint = System.UInt64; -#else -using nuint = System.UInt32; -#endif - namespace System { /// @@ -24,200 +19,35 @@ namespace System /// [DebuggerTypeProxy(typeof(SpanDebugView<>))] [DebuggerDisplay("{DebuggerDisplay,nq}")] - [NonVersionable] - public readonly ref struct ReadOnlySpan + public readonly ref partial struct ReadOnlySpan { - /// A byref or a native ptr. - internal readonly ByReference _pointer; - /// The number of elements this ReadOnlySpan contains. -#if PROJECTN - [Bound] -#endif - private readonly int _length; - - /// - /// Creates a new read-only span over the entirety of the target array. - /// - /// The target array. - /// Thrown when is a null - /// reference (Nothing in Visual Basic). - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan(T[] array) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - - _pointer = new ByReference(ref Unsafe.As(ref array.GetRawSzArrayData())); - _length = array.Length; - } - - /// - /// Creates a new read-only span over the portion of the target array beginning - /// at 'start' index and ending at 'end' index (exclusive). - /// - /// The target array. - /// The index at which to begin the read-only span. - /// The number of items in the read-only span. - /// Thrown when is a null - /// reference (Nothing in Visual Basic). - /// - /// Thrown when the specified or end index is not in the range (<0 or >=Length). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan(T[] array, int start, int length) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - _pointer = new ByReference(ref Unsafe.Add(ref Unsafe.As(ref array.GetRawSzArrayData()), start)); - _length = length; - } - - /// - /// Creates a new read-only span over the target unmanaged buffer. Clearly this - /// is quite dangerous, because we are creating arbitrarily typed T's - /// out of a void*-typed block of memory. And the length is not checked. - /// But if this creation is correct, then all subsequent uses are correct. - /// - /// An unmanaged pointer to memory. - /// The number of elements the memory contains. - /// - /// Thrown when is reference type or contains pointers and hence cannot be stored in unmanaged memory. - /// - /// - /// Thrown when the specified is negative. - /// - [CLSCompliant(false)] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe ReadOnlySpan(void* pointer, int length) - { - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); - if (length < 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - _pointer = new ByReference(ref Unsafe.As(ref *(byte*)pointer)); - _length = length; - } - - // Constructor for internal use only. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan(ref T ptr, int length) - { - Debug.Assert(length >= 0); - - _pointer = new ByReference(ref ptr); - _length = length; - } - /// /// The number of items in the read-only span. /// public int Length { +#if !FEATURE_PORTABLE_SPAN [NonVersionable] - get - { - return _length; - } - } +#endif // !FEATURE_PORTABLE_SPAN + get + { + return _length; + } + } /// /// Returns true if Length is 0. /// public bool IsEmpty { +#if !FEATURE_PORTABLE_SPAN [NonVersionable] +#endif // !FEATURE_PORTABLE_SPAN get { return _length == 0; } } - - /// - /// Returns the specified element of the read-only span. - /// - /// - /// - /// - /// Thrown when index less than 0 or index greater than or equal to Length - /// - public ref readonly T this[int index] - { -#if PROJECTN - [BoundsChecking] - get - { - return ref Unsafe.Add(ref _pointer.Value, index); - } -#else - [Intrinsic] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [NonVersionable] - get - { - if ((uint)index >= (uint)_length) - ThrowHelper.ThrowIndexOutOfRangeException(); - return ref Unsafe.Add(ref _pointer.Value, index); - } -#endif - } - - /// - /// Copies the contents of this read-only span into destination span. If the source - /// and destinations overlap, this method behaves as if the original values in - /// a temporary location before the destination is overwritten. - /// - /// The span to copy items into. - /// - /// Thrown when the destination Span is shorter than the source Span. - /// - /// - public void CopyTo(Span destination) - { - // Using "if (!TryCopyTo(...))" results in two branches: one for the length - // check, and one for the result of TryCopyTo. Since these checks are equivalent, - // we can optimize by performing the check once ourselves then calling Memmove directly. - - if ((uint)_length <= (uint)destination.Length) - { - Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length); - } - else - { - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - } - } - - /// Copies the contents of this read-only span into destination span. If the source - /// and destinations overlap, this method behaves as if the original values in - /// a temporary location before the destination is overwritten. - /// - /// If the destination span is shorter than the source span, this method - /// return false and no data is written to the destination. - /// The span to copy items into. - public bool TryCopyTo(Span destination) - { - bool retVal = false; - if ((uint)_length <= (uint)destination.Length) - { - Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length); - retVal = true; - } - return retVal; - } - - /// - /// Returns true if left and right point at the same memory and have the same length. Note that - /// this does *not* check to see if the *contents* are equal. - /// - public static bool operator ==(ReadOnlySpan left, ReadOnlySpan right) - { - return left._length == right._length && Unsafe.AreSame(ref left._pointer.Value, ref right._pointer.Value); - } - /// /// Returns false if left and right point at the same memory and have the same length. Note that /// this does *not* check to see if the *contents* are equal. @@ -251,23 +81,6 @@ namespace System } /// - /// For , returns a new instance of string that represents the characters pointed to by the span. - /// Otherwise, returns a with the name of the type and the number of elements. - /// - public override string ToString() - { - if (typeof(T) == typeof(char)) - { - unsafe - { - fixed (char* src = &Unsafe.As(ref _pointer.Value)) - return new string(src, 0, _length); - } - } - return string.Format("System.ReadOnlySpan<{0}>[{1}]", typeof(T).Name, _length); - } - - /// /// Defines an implicit conversion of an array to a /// public static implicit operator ReadOnlySpan(T[] array) => array != null ? new ReadOnlySpan(array) : default; @@ -279,54 +92,6 @@ namespace System => arraySegment.Array != null ? new ReadOnlySpan(arraySegment.Array, arraySegment.Offset, arraySegment.Count) : default; /// - /// Forms a slice out of the given read-only span, beginning at 'start'. - /// - /// The index at which to begin this slice. - /// - /// Thrown when the specified index is not in range (<0 or >=Length). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan Slice(int start) - { - if ((uint)start > (uint)_length) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - return new ReadOnlySpan(ref Unsafe.Add(ref _pointer.Value, start), _length - start); - } - - /// - /// Forms a slice out of the given read-only span, beginning at 'start', of given length - /// - /// The index at which to begin this slice. - /// The desired length for the slice (exclusive). - /// - /// Thrown when the specified or end index is not in range (<0 or >=Length). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan Slice(int start, int length) - { - if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - return new ReadOnlySpan(ref Unsafe.Add(ref _pointer.Value, start), length); - } - - /// - /// Copies the contents of this read-only span into a new array. This heap - /// allocates, so should generally be avoided, however it is sometimes - /// necessary to bridge the gap with APIs written in terms of arrays. - /// - public T[] ToArray() - { - if (_length == 0) - return Array.Empty(); - - var destination = new T[_length]; - Buffer.Memmove(ref Unsafe.As(ref destination.GetRawSzArrayData()), ref _pointer.Value, (nuint)_length); - return destination; - } - - /// /// Returns a 0-length read-only span whose base is the null pointer. /// public static ReadOnlySpan Empty => default(ReadOnlySpan); diff --git a/src/mscorlib/shared/System/Span.Fast.cs b/src/mscorlib/shared/System/Span.Fast.cs new file mode 100644 index 0000000..1382e3d --- /dev/null +++ b/src/mscorlib/shared/System/Span.Fast.cs @@ -0,0 +1,342 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; +using Internal.Runtime.CompilerServices; + +#pragma warning disable 0809 //warning CS0809: Obsolete member 'Span.Equals(object)' overrides non-obsolete member 'object.Equals(object)' + +#if BIT64 +using nuint = System.UInt64; +#else +using nuint = System.UInt32; +#endif + +namespace System +{ + /// + /// Span represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed + /// or native memory, or to memory allocated on the stack. It is type- and memory-safe. + /// + [DebuggerTypeProxy(typeof(SpanDebugView<>))] + [DebuggerDisplay("{DebuggerDisplay,nq}")] + [NonVersionable] + public readonly ref partial struct Span + { + /// A byref or a native ptr. + internal readonly ByReference _pointer; + /// The number of elements this Span contains. +#if PROJECTN + [Bound] +#endif + private readonly int _length; + + /// + /// Creates a new span over the entirety of the target array. + /// + /// The target array. + /// Thrown when is a null + /// reference (Nothing in Visual Basic). + /// Thrown when is covariant and array's type is not exactly T[]. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span(T[] array) + { + if (array == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + if (default(T) == null && array.GetType() != typeof(T[])) + ThrowHelper.ThrowArrayTypeMismatchException(); + + _pointer = new ByReference(ref Unsafe.As(ref array.GetRawSzArrayData())); + _length = array.Length; + } + + /// + /// Creates a new span over the portion of the target array beginning + /// at 'start' index and ending at 'end' index (exclusive). + /// + /// The target array. + /// The index at which to begin the span. + /// The number of items in the span. + /// Thrown when is a null + /// reference (Nothing in Visual Basic). + /// Thrown when is covariant and array's type is not exactly T[]. + /// + /// Thrown when the specified or end index is not in the range (<0 or >=Length). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span(T[] array, int start, int length) + { + if (array == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + if (default(T) == null && array.GetType() != typeof(T[])) + ThrowHelper.ThrowArrayTypeMismatchException(); + if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + _pointer = new ByReference(ref Unsafe.Add(ref Unsafe.As(ref array.GetRawSzArrayData()), start)); + _length = length; + } + + /// + /// Creates a new span over the target unmanaged buffer. Clearly this + /// is quite dangerous, because we are creating arbitrarily typed T's + /// out of a void*-typed block of memory. And the length is not checked. + /// But if this creation is correct, then all subsequent uses are correct. + /// + /// An unmanaged pointer to memory. + /// The number of elements the memory contains. + /// + /// Thrown when is reference type or contains pointers and hence cannot be stored in unmanaged memory. + /// + /// + /// Thrown when the specified is negative. + /// + [CLSCompliant(false)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe Span(void* pointer, int length) + { + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); + if (length < 0) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + _pointer = new ByReference(ref Unsafe.As(ref *(byte*)pointer)); + _length = length; + } + + // Constructor for internal use only. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Span(ref T ptr, int length) + { + Debug.Assert(length >= 0); + + _pointer = new ByReference(ref ptr); + _length = length; + } + + /// Returns a reference to specified element of the Span. + /// + /// + /// + /// + /// Thrown when index less than 0 or index greater than or equal to Length + /// + public ref T this[int index] + { +#if PROJECTN + [BoundsChecking] + get + { + return ref Unsafe.Add(ref _pointer.Value, index); + } +#else + [Intrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [NonVersionable] + get + { + if ((uint)index >= (uint)_length) + ThrowHelper.ThrowIndexOutOfRangeException(); + return ref Unsafe.Add(ref _pointer.Value, index); + } +#endif + } + + /// + /// Clears the contents of this span. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + Span.ClearWithReferences(ref Unsafe.As(ref _pointer.Value), (nuint)_length * (nuint)(Unsafe.SizeOf() / sizeof(nuint))); + } + else + { + Span.ClearWithoutReferences(ref Unsafe.As(ref _pointer.Value), (nuint)_length * (nuint)Unsafe.SizeOf()); + } + } + + /// + /// Fills the contents of this span with the given value. + /// + public void Fill(T value) + { + if (Unsafe.SizeOf() == 1) + { + uint length = (uint)_length; + if (length == 0) + return; + + T tmp = value; // Avoid taking address of the "value" argument. It would regress performance of the loop below. + Unsafe.InitBlockUnaligned(ref Unsafe.As(ref _pointer.Value), Unsafe.As(ref tmp), length); + } + else + { + // Do all math as nuint to avoid unnecessary 64->32->64 bit integer truncations + nuint length = (uint)_length; + if (length == 0) + return; + + ref T r = ref _pointer.Value; + + // TODO: Create block fill for value types of power of two sizes e.g. 2,4,8,16 + + nuint elementSize = (uint)Unsafe.SizeOf(); + nuint i = 0; + for (; i < (length & ~(nuint)7); i += 8) + { + Unsafe.AddByteOffset(ref r, (i + 0) * elementSize) = value; + Unsafe.AddByteOffset(ref r, (i + 1) * elementSize) = value; + Unsafe.AddByteOffset(ref r, (i + 2) * elementSize) = value; + Unsafe.AddByteOffset(ref r, (i + 3) * elementSize) = value; + Unsafe.AddByteOffset(ref r, (i + 4) * elementSize) = value; + Unsafe.AddByteOffset(ref r, (i + 5) * elementSize) = value; + Unsafe.AddByteOffset(ref r, (i + 6) * elementSize) = value; + Unsafe.AddByteOffset(ref r, (i + 7) * elementSize) = value; + } + if (i < (length & ~(nuint)3)) + { + Unsafe.AddByteOffset(ref r, (i + 0) * elementSize) = value; + Unsafe.AddByteOffset(ref r, (i + 1) * elementSize) = value; + Unsafe.AddByteOffset(ref r, (i + 2) * elementSize) = value; + Unsafe.AddByteOffset(ref r, (i + 3) * elementSize) = value; + i += 4; + } + for (; i < length; i++) + { + Unsafe.AddByteOffset(ref r, i * elementSize) = value; + } + } + } + + /// + /// Copies the contents of this span into destination span. If the source + /// and destinations overlap, this method behaves as if the original values in + /// a temporary location before the destination is overwritten. + /// + /// The span to copy items into. + /// + /// Thrown when the destination Span is shorter than the source Span. + /// + public void CopyTo(Span destination) + { + // Using "if (!TryCopyTo(...))" results in two branches: one for the length + // check, and one for the result of TryCopyTo. Since these checks are equivalent, + // we can optimize by performing the check once ourselves then calling Memmove directly. + + if ((uint)_length <= (uint)destination.Length) + { + Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length); + } + else + { + ThrowHelper.ThrowArgumentException_DestinationTooShort(); + } + } + + /// + /// Copies the contents of this span into destination span. If the source + /// and destinations overlap, this method behaves as if the original values in + /// a temporary location before the destination is overwritten. + /// + /// The span to copy items into. + /// If the destination span is shorter than the source span, this method + /// return false and no data is written to the destination. + public bool TryCopyTo(Span destination) + { + bool retVal = false; + if ((uint)_length <= (uint)destination.Length) + { + Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length); + retVal = true; + } + return retVal; + } + + /// + /// Returns true if left and right point at the same memory and have the same length. Note that + /// this does *not* check to see if the *contents* are equal. + /// + public static bool operator ==(Span left, Span right) + { + return left._length == right._length && Unsafe.AreSame(ref left._pointer.Value, ref right._pointer.Value); + } + + /// + /// Defines an implicit conversion of a to a + /// + public static implicit operator ReadOnlySpan(Span span) => new ReadOnlySpan(ref span._pointer.Value, span._length); + + /// + /// For , returns a new instance of string that represents the characters pointed to by the span. + /// Otherwise, returns a with the name of the type and the number of elements. + /// + public override string ToString() + { + if (typeof(T) == typeof(char)) + { + unsafe + { + fixed (char* src = &Unsafe.As(ref _pointer.Value)) + return new string(src, 0, _length); + } + } + return string.Format("System.Span<{0}>[{1}]", typeof(T).Name, _length); + } + + /// + /// Forms a slice out of the given span, beginning at 'start'. + /// + /// The index at which to begin this slice. + /// + /// Thrown when the specified index is not in range (<0 or >=Length). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span Slice(int start) + { + if ((uint)start > (uint)_length) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + return new Span(ref Unsafe.Add(ref _pointer.Value, start), _length - start); + } + + /// + /// Forms a slice out of the given span, beginning at 'start', of given length + /// + /// The index at which to begin this slice. + /// The desired length for the slice (exclusive). + /// + /// Thrown when the specified or end index is not in range (<0 or >=Length). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span Slice(int start, int length) + { + if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + return new Span(ref Unsafe.Add(ref _pointer.Value, start), length); + } + + /// + /// Copies the contents of this span into a new array. This heap + /// allocates, so should generally be avoided, however it is sometimes + /// necessary to bridge the gap with APIs written in terms of arrays. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T[] ToArray() + { + if (_length == 0) + return Array.Empty(); + + var destination = new T[_length]; + Buffer.Memmove(ref Unsafe.As(ref destination.GetRawSzArrayData()), ref _pointer.Value, (nuint)_length); + return destination; + } + } +} diff --git a/src/mscorlib/shared/System/Span.cs b/src/mscorlib/shared/System/Span.cs index fd5ad26..f591811 100644 --- a/src/mscorlib/shared/System/Span.cs +++ b/src/mscorlib/shared/System/Span.cs @@ -5,17 +5,12 @@ using System.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; +#if !FEATURE_PORTABLE_SPAN using System.Runtime.Versioning; -using Internal.Runtime.CompilerServices; +#endif // !FEATURE_PORTABLE_SPAN #pragma warning disable 0809 //warning CS0809: Obsolete member 'Span.Equals(object)' overrides non-obsolete member 'object.Equals(object)' -#if BIT64 -using nuint = System.UInt64; -#else -using nuint = System.UInt32; -#endif - namespace System { /// @@ -24,118 +19,30 @@ namespace System /// [DebuggerTypeProxy(typeof(SpanDebugView<>))] [DebuggerDisplay("{DebuggerDisplay,nq}")] - [NonVersionable] - public readonly ref struct Span + public readonly ref partial struct Span { - /// A byref or a native ptr. - internal readonly ByReference _pointer; - /// The number of elements this Span contains. -#if PROJECTN - [Bound] -#endif - private readonly int _length; - - /// - /// Creates a new span over the entirety of the target array. - /// - /// The target array. - /// Thrown when is a null - /// reference (Nothing in Visual Basic). - /// Thrown when is covariant and array's type is not exactly T[]. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span(T[] array) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - if (default(T) == null && array.GetType() != typeof(T[])) - ThrowHelper.ThrowArrayTypeMismatchException(); - - _pointer = new ByReference(ref Unsafe.As(ref array.GetRawSzArrayData())); - _length = array.Length; - } - - /// - /// Creates a new span over the portion of the target array beginning - /// at 'start' index and ending at 'end' index (exclusive). - /// - /// The target array. - /// The index at which to begin the span. - /// The number of items in the span. - /// Thrown when is a null - /// reference (Nothing in Visual Basic). - /// Thrown when is covariant and array's type is not exactly T[]. - /// - /// Thrown when the specified or end index is not in the range (<0 or >=Length). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span(T[] array, int start, int length) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - if (default(T) == null && array.GetType() != typeof(T[])) - ThrowHelper.ThrowArrayTypeMismatchException(); - if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - _pointer = new ByReference(ref Unsafe.Add(ref Unsafe.As(ref array.GetRawSzArrayData()), start)); - _length = length; - } - - /// - /// Creates a new span over the target unmanaged buffer. Clearly this - /// is quite dangerous, because we are creating arbitrarily typed T's - /// out of a void*-typed block of memory. And the length is not checked. - /// But if this creation is correct, then all subsequent uses are correct. - /// - /// An unmanaged pointer to memory. - /// The number of elements the memory contains. - /// - /// Thrown when is reference type or contains pointers and hence cannot be stored in unmanaged memory. - /// - /// - /// Thrown when the specified is negative. - /// - [CLSCompliant(false)] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe Span(void* pointer, int length) - { - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); - if (length < 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - _pointer = new ByReference(ref Unsafe.As(ref *(byte*)pointer)); - _length = length; - } - - // Constructor for internal use only. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Span(ref T ptr, int length) - { - Debug.Assert(length >= 0); - - _pointer = new ByReference(ref ptr); - _length = length; - } - /// /// The number of items in the span. /// public int Length { +#if !FEATURE_PORTABLE_SPAN [NonVersionable] - get - { - return _length; - } - } +#endif // !FEATURE_PORTABLE_SPAN + get + { + return _length; + } + } /// /// Returns true if Length is 0. /// public bool IsEmpty { +#if !FEATURE_PORTABLE_SPAN [NonVersionable] +#endif // !FEATURE_PORTABLE_SPAN get { return _length == 0; @@ -143,157 +50,6 @@ namespace System } /// - /// Returns a reference to specified element of the Span. - /// - /// - /// - /// - /// Thrown when index less than 0 or index greater than or equal to Length - /// - public ref T this[int index] - { -#if PROJECTN - [BoundsChecking] - get - { - return ref Unsafe.Add(ref _pointer.Value, index); - } -#else - [Intrinsic] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [NonVersionable] - get - { - if ((uint)index >= (uint)_length) - ThrowHelper.ThrowIndexOutOfRangeException(); - return ref Unsafe.Add(ref _pointer.Value, index); - } -#endif - } - - /// - /// Clears the contents of this span. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() - { - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - Span.ClearWithReferences(ref Unsafe.As(ref _pointer.Value), (nuint)_length * (nuint)(Unsafe.SizeOf() / sizeof(nuint))); - } - else - { - Span.ClearWithoutReferences(ref Unsafe.As(ref _pointer.Value), (nuint)_length * (nuint)Unsafe.SizeOf()); - } - } - - /// - /// Fills the contents of this span with the given value. - /// - public void Fill(T value) - { - if (Unsafe.SizeOf() == 1) - { - uint length = (uint)_length; - if (length == 0) - return; - - T tmp = value; // Avoid taking address of the "value" argument. It would regress performance of the loop below. - Unsafe.InitBlockUnaligned(ref Unsafe.As(ref _pointer.Value), Unsafe.As(ref tmp), length); - } - else - { - // Do all math as nuint to avoid unnecessary 64->32->64 bit integer truncations - nuint length = (uint)_length; - if (length == 0) - return; - - ref T r = ref _pointer.Value; - - // TODO: Create block fill for value types of power of two sizes e.g. 2,4,8,16 - - nuint elementSize = (uint)Unsafe.SizeOf(); - nuint i = 0; - for (; i < (length & ~(nuint)7); i += 8) - { - Unsafe.AddByteOffset(ref r, (i + 0) * elementSize) = value; - Unsafe.AddByteOffset(ref r, (i + 1) * elementSize) = value; - Unsafe.AddByteOffset(ref r, (i + 2) * elementSize) = value; - Unsafe.AddByteOffset(ref r, (i + 3) * elementSize) = value; - Unsafe.AddByteOffset(ref r, (i + 4) * elementSize) = value; - Unsafe.AddByteOffset(ref r, (i + 5) * elementSize) = value; - Unsafe.AddByteOffset(ref r, (i + 6) * elementSize) = value; - Unsafe.AddByteOffset(ref r, (i + 7) * elementSize) = value; - } - if (i < (length & ~(nuint)3)) - { - Unsafe.AddByteOffset(ref r, (i + 0) * elementSize) = value; - Unsafe.AddByteOffset(ref r, (i + 1) * elementSize) = value; - Unsafe.AddByteOffset(ref r, (i + 2) * elementSize) = value; - Unsafe.AddByteOffset(ref r, (i + 3) * elementSize) = value; - i += 4; - } - for (; i < length; i++) - { - Unsafe.AddByteOffset(ref r, i * elementSize) = value; - } - } - } - - /// - /// Copies the contents of this span into destination span. If the source - /// and destinations overlap, this method behaves as if the original values in - /// a temporary location before the destination is overwritten. - /// - /// The span to copy items into. - /// - /// Thrown when the destination Span is shorter than the source Span. - /// - public void CopyTo(Span destination) - { - // Using "if (!TryCopyTo(...))" results in two branches: one for the length - // check, and one for the result of TryCopyTo. Since these checks are equivalent, - // we can optimize by performing the check once ourselves then calling Memmove directly. - - if ((uint)_length <= (uint)destination.Length) - { - Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length); - } - else - { - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - } - } - - /// - /// Copies the contents of this span into destination span. If the source - /// and destinations overlap, this method behaves as if the original values in - /// a temporary location before the destination is overwritten. - /// - /// The span to copy items into. - /// If the destination span is shorter than the source span, this method - /// return false and no data is written to the destination. - public bool TryCopyTo(Span destination) - { - bool retVal = false; - if ((uint)_length <= (uint)destination.Length) - { - Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length); - retVal = true; - } - return retVal; - } - - /// - /// Returns true if left and right point at the same memory and have the same length. Note that - /// this does *not* check to see if the *contents* are equal. - /// - public static bool operator ==(Span left, Span right) - { - return left._length == right._length && Unsafe.AreSame(ref left._pointer.Value, ref right._pointer.Value); - } - - /// /// Returns false if left and right point at the same memory and have the same length. Note that /// this does *not* check to see if the *contents* are equal. /// @@ -326,23 +82,6 @@ namespace System } /// - /// For , returns a new instance of string that represents the characters pointed to by the span. - /// Otherwise, returns a with the name of the type and the number of elements. - /// - public override string ToString() - { - if (typeof(T) == typeof(char)) - { - unsafe - { - fixed (char* src = &Unsafe.As(ref _pointer.Value)) - return new string(src, 0, _length); - } - } - return string.Format("System.Span<{0}>[{1}]", typeof(T).Name, _length); - } - - /// /// Defines an implicit conversion of an array to a /// public static implicit operator Span(T[] array) => array != null ? new Span(array) : default; @@ -354,60 +93,6 @@ namespace System => arraySegment.Array != null ? new Span(arraySegment.Array, arraySegment.Offset, arraySegment.Count) : default; /// - /// Defines an implicit conversion of a to a - /// - public static implicit operator ReadOnlySpan(Span span) => new ReadOnlySpan(ref span._pointer.Value, span._length); - - /// - /// Forms a slice out of the given span, beginning at 'start'. - /// - /// The index at which to begin this slice. - /// - /// Thrown when the specified index is not in range (<0 or >=Length). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span Slice(int start) - { - if ((uint)start > (uint)_length) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - return new Span(ref Unsafe.Add(ref _pointer.Value, start), _length - start); - } - - /// - /// Forms a slice out of the given span, beginning at 'start', of given length - /// - /// The index at which to begin this slice. - /// The desired length for the slice (exclusive). - /// - /// Thrown when the specified or end index is not in range (<0 or >=Length). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span Slice(int start, int length) - { - if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - return new Span(ref Unsafe.Add(ref _pointer.Value, start), length); - } - - /// - /// Copies the contents of this span into a new array. This heap - /// allocates, so should generally be avoided, however it is sometimes - /// necessary to bridge the gap with APIs written in terms of arrays. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T[] ToArray() - { - if (_length == 0) - return Array.Empty(); - - var destination = new T[_length]; - Buffer.Memmove(ref Unsafe.As(ref destination.GetRawSzArrayData()), ref _pointer.Value, (nuint)_length); - return destination; - } - - // /// Returns an empty /// public static Span Empty => default(Span); -- 2.7.4