Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Span factoring (#26667)
Browse files Browse the repository at this point in the history
  • Loading branch information
tarekgh authored and jkotas committed Feb 2, 2018
1 parent c727d12 commit a68803c
Show file tree
Hide file tree
Showing 8 changed files with 638 additions and 795 deletions.
262 changes: 262 additions & 0 deletions src/Common/src/CoreLib/System/ReadOnlySpan.Fast.cs
Original file line number Diff line number Diff line change
@@ -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<T>.Equals(object)' overrides non-obsolete member 'object.Equals(object)'

#if BIT64
using nuint = System.UInt64;
#else
using nuint = System.UInt32;
#endif

namespace System
{
/// <summary>
/// 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.
/// </summary>
[DebuggerTypeProxy(typeof(SpanDebugView<>))]
[DebuggerDisplay("{DebuggerDisplay,nq}")]
[NonVersionable]
public readonly ref partial struct ReadOnlySpan<T>
{
/// <summary>A byref or a native ptr.</summary>
internal readonly ByReference<T> _pointer;
/// <summary>The number of elements this ReadOnlySpan contains.</summary>
#if PROJECTN
[Bound]
#endif
private readonly int _length;

/// <summary>
/// Creates a new read-only span over the entirety of the target array.
/// </summary>
/// <param name="array">The target array.</param>
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="array"/> is a null
/// reference (Nothing in Visual Basic).</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan(T[] array)
{
if (array == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);

_pointer = new ByReference<T>(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()));
_length = array.Length;
}

/// <summary>
/// Creates a new read-only span over the portion of the target array beginning
/// at 'start' index and ending at 'end' index (exclusive).
/// </summary>
/// <param name="array">The target array.</param>
/// <param name="start">The index at which to begin the read-only span.</param>
/// <param name="length">The number of items in the read-only span.</param>
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="array"/> is a null
/// reference (Nothing in Visual Basic).</exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=Length).
/// </exception>
[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<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start));
_length = length;
}

/// <summary>
/// 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.
/// </summary>
/// <param name="pointer">An unmanaged pointer to memory.</param>
/// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param>
/// <exception cref="System.ArgumentException">
/// Thrown when <typeparamref name="T"/> is reference type or contains pointers and hence cannot be stored in unmanaged memory.
/// </exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// Thrown when the specified <paramref name="length"/> is negative.
/// </exception>
[CLSCompliant(false)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe ReadOnlySpan(void* pointer, int length)
{
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
if (length < 0)
ThrowHelper.ThrowArgumentOutOfRangeException();

_pointer = new ByReference<T>(ref Unsafe.As<byte, T>(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<T>(ref ptr);
_length = length;
}

/// <summary>
/// Returns the specified element of the read-only span.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
/// <exception cref="System.IndexOutOfRangeException">
/// Thrown when index less than 0 or index greater than or equal to Length
/// </exception>
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
}

/// <summary>
/// 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.
///
/// <param name="destination">The span to copy items into.</param>
/// <exception cref="System.ArgumentException">
/// Thrown when the destination Span is shorter than the source Span.
/// </exception>
/// </summary>
public void CopyTo(Span<T> 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.
/// </summary>
/// <returns>If the destination span is shorter than the source span, this method
/// return false and no data is written to the destination.</returns>
/// <param name="destination">The span to copy items into.</param>
public bool TryCopyTo(Span<T> 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;
}

/// <summary>
/// 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.
/// </summary>
public static bool operator ==(ReadOnlySpan<T> left, ReadOnlySpan<T> right)
{
return left._length == right._length && Unsafe.AreSame<T>(ref left._pointer.Value, ref right._pointer.Value);
}

/// <summary>
/// For <see cref="ReadOnlySpan{Char}"/>, returns a new instance of string that represents the characters pointed to by the span.
/// Otherwise, returns a <see cref="String"/> with the name of the type and the number of elements.
/// </summary>
public override string ToString()
{
if (typeof(T) == typeof(char))
{
unsafe
{
fixed (char* src = &Unsafe.As<T, char>(ref _pointer.Value))
return new string(src, 0, _length);
}
}
return string.Format("System.ReadOnlySpan<{0}>[{1}]", typeof(T).Name, _length);
}

/// <summary>
/// Forms a slice out of the given read-only span, beginning at 'start'.
/// </summary>
/// <param name="start">The index at which to begin this slice.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;=Length).
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<T> Slice(int start)
{
if ((uint)start > (uint)_length)
ThrowHelper.ThrowArgumentOutOfRangeException();

return new ReadOnlySpan<T>(ref Unsafe.Add(ref _pointer.Value, start), _length - start);
}

/// <summary>
/// Forms a slice out of the given read-only span, beginning at 'start', of given length
/// </summary>
/// <param name="start">The index at which to begin this slice.</param>
/// <param name="length">The desired length for the slice (exclusive).</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// Thrown when the specified <paramref name="start"/> or end index is not in range (&lt;0 or &gt;=Length).
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<T> Slice(int start, int length)
{
if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
ThrowHelper.ThrowArgumentOutOfRangeException();

return new ReadOnlySpan<T>(ref Unsafe.Add(ref _pointer.Value, start), length);
}

/// <summary>
/// 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.
/// </summary>
public T[] ToArray()
{
if (_length == 0)
return Array.Empty<T>();

var destination = new T[_length];
Buffer.Memmove(ref Unsafe.As<byte, T>(ref destination.GetRawSzArrayData()), ref _pointer.Value, (nuint)_length);
return destination;
}
}
}
Loading

0 comments on commit a68803c

Please sign in to comment.