diff options
author | Egor Bogatov <egorbo@gmail.com> | 2019-09-26 17:59:39 +0300 |
---|---|---|
committer | Steve Pfister <steveisok@users.noreply.github.com> | 2019-09-26 17:59:39 +0300 |
commit | 4e02deca04fe66935c76e1475bd9e00f4377821f (patch) | |
tree | 0f0b79f03431b867af5ed1b77921668cceb03e06 /src | |
parent | 65712075da8062eb2d9304104c110185001f8541 (diff) |
Reflect recent NS21 changes (#348)
Diffstat (limited to 'src')
6 files changed, 574 insertions, 213 deletions
diff --git a/src/Common/src/CoreLib/System/Math.cs b/src/Common/src/CoreLib/System/Math.cs index 27ef7752de..96795af41e 100644 --- a/src/Common/src/CoreLib/System/Math.cs +++ b/src/Common/src/CoreLib/System/Math.cs @@ -132,6 +132,20 @@ namespace System return div; } + internal static uint DivRem(uint a, uint b, out uint result) + { + uint div = a / b; + result = a - (div * b); + return div; + } + + internal static ulong DivRem(ulong a, ulong b, out ulong result) + { + ulong div = a / b; + result = a - (div * b); + return div; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static decimal Ceiling(decimal d) { diff --git a/src/System.Data.DataSetExtensions/src/System/Data/DataTableExtensions.cs b/src/System.Data.DataSetExtensions/src/System/Data/DataTableExtensions.cs index 593edb080d..a66ae22c0e 100644 --- a/src/System.Data.DataSetExtensions/src/System/Data/DataTableExtensions.cs +++ b/src/System.Data.DataSetExtensions/src/System/Data/DataTableExtensions.cs @@ -218,5 +218,27 @@ namespace System.Data Debug.Assert(null != table, "null DataTable"); return table; } + + + /// <summary> + /// Creates a LinkDataView of DataRow over the input table. + /// </summary> + /// <param name="table">DataTable that the view is over.</param> + /// <returns>An instance of LinkDataView.</returns> + public static DataView AsDataView(this DataTable table) + { + throw new PlatformNotSupportedException (); + } + + /// <summary> + /// Creates a LinqDataView from EnumerableDataTable + /// </summary> + /// <typeparam name="T">Type of the row in the table. Must inherit from DataRow</typeparam> + /// <param name="source">The enumerable-datatable over which view must be created.</param> + /// <returns>Generated LinkDataView of type T</returns> + public static DataView AsDataView<T>(this EnumerableRowCollection<T> source) where T : DataRow + { + throw new PlatformNotSupportedException (); + } } } diff --git a/src/System.Memory/src/System/Buffers/ReadOnlySequence.cs b/src/System.Memory/src/System/Buffers/ReadOnlySequence.cs index f8d4046557..34a2997cd3 100644 --- a/src/System.Memory/src/System/Buffers/ReadOnlySequence.cs +++ b/src/System.Memory/src/System/Buffers/ReadOnlySequence.cs @@ -3,11 +3,10 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#if !FEATURE_PORTABLE_SPAN using Internal.Runtime.CompilerServices; -#endif // FEATURE_PORTABLE_SPAN namespace System.Buffers { @@ -18,17 +17,16 @@ namespace System.Buffers [DebuggerDisplay("{ToString(),raw}")] public readonly partial struct ReadOnlySequence<T> { - private readonly SequencePosition _sequenceStart; - private readonly SequencePosition _sequenceEnd; + // The data is essentially two SequencePositions, however the Start and End SequencePositions are deconstructed to improve packing. + private readonly object _startObject; + private readonly object _endObject; + private readonly int _startInteger; + private readonly int _endInteger; /// <summary> /// Returns empty <see cref="ReadOnlySequence{T}"/> /// </summary> -#if FEATURE_PORTABLE_SPAN - public static readonly ReadOnlySequence<T> Empty = new ReadOnlySequence<T>(SpanHelpers.PerTypeValues<T>.EmptyArray); -#else public static readonly ReadOnlySequence<T> Empty = new ReadOnlySequence<T>(Array.Empty<T>()); -#endif // FEATURE_PORTABLE_SPAN /// <summary> /// Length of the <see cref="ReadOnlySequence{T}"/>. @@ -46,7 +44,7 @@ namespace System.Buffers public bool IsSingleSegment { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _sequenceStart.GetObject() == _sequenceEnd.GetObject(); + get => _startObject == _endObject; } /// <summary> @@ -55,14 +53,27 @@ namespace System.Buffers public ReadOnlyMemory<T> First => GetFirstBuffer(); /// <summary> + /// Gets <see cref="ReadOnlySpan{T}"/> from the first segment. + /// </summary> + public ReadOnlySpan<T> FirstSpan => GetFirstSpan(); + + /// <summary> /// A position to the start of the <see cref="ReadOnlySequence{T}"/>. /// </summary> - public SequencePosition Start => _sequenceStart; + public SequencePosition Start + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new SequencePosition(_startObject, _startInteger); + } /// <summary> /// A position to the end of the <see cref="ReadOnlySequence{T}"/> /// </summary> - public SequencePosition End => _sequenceEnd; + public SequencePosition End + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new SequencePosition(_endObject, _endInteger); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] private ReadOnlySequence(object startSegment, int startIndexAndFlags, object endSegment, int endIndexAndFlags) @@ -73,8 +84,10 @@ namespace System.Buffers Debug.Assert((startSegment != null && endSegment != null) || (startSegment == null && endSegment == null && startIndexAndFlags == 0 && endIndexAndFlags == 0)); - _sequenceStart = new SequencePosition(startSegment, startIndexAndFlags); - _sequenceEnd = new SequencePosition(endSegment, endIndexAndFlags); + _startObject = startSegment; + _endObject = endSegment; + _startInteger = startIndexAndFlags; + _endInteger = endIndexAndFlags; } /// <summary> @@ -91,24 +104,28 @@ namespace System.Buffers (startSegment == endSegment && endIndex < startIndex)) ThrowHelper.ThrowArgumentValidationException(startSegment, startIndex, endSegment); - _sequenceStart = new SequencePosition(startSegment, ReadOnlySequence.SegmentToSequenceStart(startIndex)); - _sequenceEnd = new SequencePosition(endSegment, ReadOnlySequence.SegmentToSequenceEnd(endIndex)); + _startObject = startSegment; + _endObject = endSegment; + _startInteger = ReadOnlySequence.SegmentToSequenceStart(startIndex); + _endInteger = ReadOnlySequence.SegmentToSequenceEnd(endIndex); } /// <summary> - /// Creates an instance of <see cref="ReadOnlySequence{T}"/> from the <see cref="T:T[]"/>. + /// Creates an instance of <see cref="ReadOnlySequence{T}"/> from the array. /// </summary> public ReadOnlySequence(T[] array) { if (array == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - _sequenceStart = new SequencePosition(array, ReadOnlySequence.ArrayToSequenceStart(0)); - _sequenceEnd = new SequencePosition(array, ReadOnlySequence.ArrayToSequenceEnd(array.Length)); + _startObject = array; + _endObject = array; + _startInteger = ReadOnlySequence.ArrayToSequenceStart(0); + _endInteger = ReadOnlySequence.ArrayToSequenceEnd(array!.Length); // TODO-NULLABLE: Remove ! when [DoesNotReturn] respected } /// <summary> - /// Creates an instance of <see cref="ReadOnlySequence{T}"/> from the <see cref="T:T[]"/>, start and index. + /// Creates an instance of <see cref="ReadOnlySequence{T}"/> from the array, start, and index. /// </summary> public ReadOnlySequence(T[] array, int start, int length) { @@ -117,8 +134,10 @@ namespace System.Buffers (uint)length > (uint)(array.Length - start)) ThrowHelper.ThrowArgumentValidationException(array, start); - _sequenceStart = new SequencePosition(array, ReadOnlySequence.ArrayToSequenceStart(start)); - _sequenceEnd = new SequencePosition(array, ReadOnlySequence.ArrayToSequenceEnd(start + length)); + _startObject = array; + _endObject = array; + _startInteger = ReadOnlySequence.ArrayToSequenceStart(start); + _endInteger = ReadOnlySequence.ArrayToSequenceEnd(start + length); } /// <summary> @@ -127,40 +146,51 @@ namespace System.Buffers /// </summary> public ReadOnlySequence(ReadOnlyMemory<T> memory) { +#pragma warning disable CS8631 // TODO-NULLABLE: ILLink rewriter removing some necessary metadata (https://github.com/dotnet/corefx/pull/38983#issuecomment-506757237) if (MemoryMarshal.TryGetMemoryManager(memory, out MemoryManager<T> manager, out int index, out int length)) +#pragma warning restore CS8631 { - _sequenceStart = new SequencePosition(manager, ReadOnlySequence.MemoryManagerToSequenceStart(index)); - _sequenceEnd = new SequencePosition(manager, ReadOnlySequence.MemoryManagerToSequenceEnd(length)); + _startObject = manager; + _endObject = manager; + _startInteger = ReadOnlySequence.MemoryManagerToSequenceStart(index); + _endInteger = ReadOnlySequence.MemoryManagerToSequenceEnd(length); } else if (MemoryMarshal.TryGetArray(memory, out ArraySegment<T> segment)) { T[] array = segment.Array; int start = segment.Offset; - _sequenceStart = new SequencePosition(array, ReadOnlySequence.ArrayToSequenceStart(start)); - _sequenceEnd = new SequencePosition(array, ReadOnlySequence.ArrayToSequenceEnd(start + segment.Count)); + _startObject = array; + _endObject = array; + _startInteger = ReadOnlySequence.ArrayToSequenceStart(start); + _endInteger = ReadOnlySequence.ArrayToSequenceEnd(start + segment.Count); } else if (typeof(T) == typeof(char)) { - if (!MemoryMarshal.TryGetString(((ReadOnlyMemory<char>)(object)memory), out string text, out int start, out length)) + if (!MemoryMarshal.TryGetString((ReadOnlyMemory<char>)(object)memory, out string text, out int start, out length)) ThrowHelper.ThrowInvalidOperationException(); - _sequenceStart = new SequencePosition(text, ReadOnlySequence.StringToSequenceStart(start)); - _sequenceEnd = new SequencePosition(text, ReadOnlySequence.StringToSequenceEnd(start + length)); + _startObject = text; + _endObject = text; + _startInteger = ReadOnlySequence.StringToSequenceStart(start); + _endInteger = ReadOnlySequence.StringToSequenceEnd(start + length); } else { // Should never be reached ThrowHelper.ThrowInvalidOperationException(); - _sequenceStart = default; - _sequenceEnd = default; + _startObject = null; + _endObject = null; + _startInteger = 0; + _endInteger = 0; } } /// <summary> - /// Forms a slice out of the given <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, with <paramref name="length"/> items + /// Forms a slice out of the current <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, with <paramref name="length"/> items. /// </summary> /// <param name="start">The index at which to begin this slice.</param> - /// <param name="length">The length of the slice</param> + /// <param name="length">The length of the slice.</param> + /// <returns>A slice that consists of <paramref name="length" /> elements from the current instance starting at index <paramref name="start" />.</returns> public ReadOnlySequence<T> Slice(long start, long length) { if (start < 0 || length < 0) @@ -169,11 +199,11 @@ namespace System.Buffers SequencePosition begin; SequencePosition end; - int startIndex = GetIndex(_sequenceStart); - int endIndex = GetIndex(_sequenceEnd); + int startIndex = GetIndex(_startInteger); + int endIndex = GetIndex(_endInteger); - object startObject = _sequenceStart.GetObject(); - object endObject = _sequenceEnd.GetObject(); + object startObject = _startObject; + object endObject = _endObject; if (startObject != endObject) { @@ -188,22 +218,22 @@ namespace System.Buffers startIndex += (int)start; begin = new SequencePosition(startObject, startIndex); - end = GetEndPosition(startSegment, startObject, startIndex, endObject, endIndex, length); + end = GetEndPosition(startSegment, startObject, startIndex, endObject!, endIndex, length); } else { if (currentLength < 0) ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange(); - begin = SeekMultiSegment(startSegment.Next, endObject, endIndex, start - currentLength, ExceptionArgument.start); + begin = SeekMultiSegment(startSegment.Next!, endObject!, endIndex, start - currentLength, ExceptionArgument.start); int beginIndex = GetIndex(begin); - object beginObject = begin.GetObject(); + object beginObject = begin.GetObject()!; if (beginObject != endObject) { Debug.Assert(beginObject != null); - end = GetEndPosition((ReadOnlySequenceSegment<T>)beginObject, beginObject, beginIndex, endObject, endIndex, length); + end = GetEndPosition((ReadOnlySequenceSegment<T>)beginObject, beginObject, beginIndex, endObject!, endIndex, length); } else { @@ -232,23 +262,30 @@ namespace System.Buffers } /// <summary> - /// Forms a slice out of the given <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, ending at <paramref name="end"/> (inclusive). + /// Forms a slice out of the current <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/> and ending at <paramref name="end"/> (exclusive). /// </summary> /// <param name="start">The index at which to begin this slice.</param> - /// <param name="end">The end (inclusive) of the slice</param> + /// <param name="end">The ending (exclusive) <see cref="SequencePosition"/> of the slice.</param> + /// <returns>A slice that consists of items from the <paramref name="start" /> index to, but not including, the <paramref name="end" /> sequence position in the current read-only sequence.</returns> public ReadOnlySequence<T> Slice(long start, SequencePosition end) { if (start < 0) ThrowHelper.ThrowStartOrEndArgumentValidationException(start); + uint startIndex = (uint)GetIndex(_startInteger); + object startObject = _startObject; + + uint endIndex = (uint)GetIndex(_endInteger); + object endObject = _endObject; + uint sliceEndIndex = (uint)GetIndex(end); object sliceEndObject = end.GetObject(); - uint startIndex = (uint)GetIndex(_sequenceStart); - object startObject = _sequenceStart.GetObject(); - - uint endIndex = (uint)GetIndex(_sequenceEnd); - object endObject = _sequenceEnd.GetObject(); + if (sliceEndObject == null) + { + sliceEndObject = _startObject; + sliceEndIndex = startIndex; + } // Single-Segment Sequence if (startObject == endObject) @@ -265,16 +302,16 @@ namespace System.Buffers } // Multi-Segment Sequence - var startSegment = (ReadOnlySequenceSegment<T>)startObject; + var startSegment = (ReadOnlySequenceSegment<T>)startObject!; ulong startRange = (ulong)(startSegment.RunningIndex + startIndex); - ulong sliceRange = (ulong)(((ReadOnlySequenceSegment<T>)sliceEndObject).RunningIndex + sliceEndIndex); + ulong sliceRange = (ulong)(((ReadOnlySequenceSegment<T>)sliceEndObject!).RunningIndex + sliceEndIndex); // This optimization works because we know sliceEndIndex, startIndex, and endIndex are all >= 0 Debug.Assert(sliceEndIndex >= 0 && startIndex >= 0 && endIndex >= 0); if (!InRange( sliceRange, startRange, - (ulong)(((ReadOnlySequenceSegment<T>)endObject).RunningIndex + endIndex))) + (ulong)(((ReadOnlySequenceSegment<T>)endObject!).RunningIndex + endIndex))) { ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange(); } @@ -293,32 +330,39 @@ namespace System.Buffers ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange(); // End of segment. Move to start of next. - SequencePosition begin = SeekMultiSegment(startSegment.Next, sliceEndObject, (int)sliceEndIndex, start - currentLength, ExceptionArgument.start); + SequencePosition begin = SeekMultiSegment(startSegment.Next!, sliceEndObject, (int)sliceEndIndex, start - currentLength, ExceptionArgument.start); return SliceImpl(begin, end); } FoundInFirstSegment: // startIndex + start <= int.MaxValue Debug.Assert(start <= int.MaxValue - startIndex); - return SliceImpl(new SequencePosition(startObject, (int)startIndex + (int)start), end); + return SliceImpl(new SequencePosition(startObject, (int)startIndex + (int)start), new SequencePosition(sliceEndObject, (int)sliceEndIndex)); } /// <summary> - /// Forms a slice out of the given <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, with <paramref name="length"/> items + /// Forms a slice out of the current <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, with <paramref name="length"/> items. /// </summary> /// <param name="start">The starting (inclusive) <see cref="SequencePosition"/> at which to begin this slice.</param> - /// <param name="length">The length of the slice</param> + /// <param name="length">The length of the slice.</param> + /// <returns>A slice that consists of <paramref name="length" /> elements from the current instance starting at sequence position <paramref name="start" />.</returns> public ReadOnlySequence<T> Slice(SequencePosition start, long length) { + uint startIndex = (uint)GetIndex(_startInteger); + object startObject = _startObject; + + uint endIndex = (uint)GetIndex(_endInteger); + object endObject = _endObject; + // Check start before length uint sliceStartIndex = (uint)GetIndex(start); object sliceStartObject = start.GetObject(); - uint startIndex = (uint)GetIndex(_sequenceStart); - object startObject = _sequenceStart.GetObject(); - - uint endIndex = (uint)GetIndex(_sequenceEnd); - object endObject = _sequenceEnd.GetObject(); + if (sliceStartObject == null) + { + sliceStartIndex = startIndex; + sliceStartObject = _startObject; + } // Single-Segment Sequence if (startObject == endObject) @@ -339,10 +383,10 @@ namespace System.Buffers } // Multi-Segment Sequence - var sliceStartSegment = (ReadOnlySequenceSegment<T>)sliceStartObject; + var sliceStartSegment = (ReadOnlySequenceSegment<T>)sliceStartObject!; ulong sliceRange = (ulong)((sliceStartSegment.RunningIndex + sliceStartIndex)); - ulong startRange = (ulong)(((ReadOnlySequenceSegment<T>)startObject).RunningIndex + startIndex); - ulong endRange = (ulong)(((ReadOnlySequenceSegment<T>)endObject).RunningIndex + endIndex); + ulong startRange = (ulong)(((ReadOnlySequenceSegment<T>)startObject!).RunningIndex + startIndex); + ulong endRange = (ulong)(((ReadOnlySequenceSegment<T>)endObject!).RunningIndex + endIndex); // This optimization works because we know sliceStartIndex, startIndex, and endIndex are all >= 0 Debug.Assert(sliceStartIndex >= 0 && startIndex >= 0 && endIndex >= 0); @@ -369,42 +413,46 @@ namespace System.Buffers ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange(); // End of segment. Move to start of next. - SequencePosition end = SeekMultiSegment(sliceStartSegment.Next, endObject, (int)endIndex, length - currentLength, ExceptionArgument.length); + SequencePosition end = SeekMultiSegment(sliceStartSegment.Next!, endObject, (int)endIndex, length - currentLength, ExceptionArgument.length); return SliceImpl(start, end); } FoundInFirstSegment: // sliceStartIndex + length <= int.MaxValue Debug.Assert(length <= int.MaxValue - sliceStartIndex); - return SliceImpl(start, new SequencePosition(sliceStartObject, (int)sliceStartIndex + (int)length)); + return SliceImpl(new SequencePosition(sliceStartObject, (int)sliceStartIndex), new SequencePosition(sliceStartObject, (int)sliceStartIndex + (int)length)); } /// <summary> - /// Forms a slice out of the given <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, with <paramref name="length"/> items + /// Forms a slice out of the current <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, with <paramref name="length"/> items. /// </summary> /// <param name="start">The index at which to begin this slice.</param> - /// <param name="length">The length of the slice</param> + /// <param name="length">The length of the slice.</param> + /// <returns>A slice that consists of <paramref name="length" /> elements from the current instance starting at index <paramref name="start" />.</returns> public ReadOnlySequence<T> Slice(int start, int length) => Slice((long)start, length); /// <summary> - /// Forms a slice out of the given <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, ending at <paramref name="end"/> (inclusive). + /// Forms a slice out of the current <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/> and ending at <paramref name="end"/> (exclusive). /// </summary> /// <param name="start">The index at which to begin this slice.</param> - /// <param name="end">The end (inclusive) of the slice</param> + /// <param name="end">The ending (exclusive) <see cref="SequencePosition"/> of the slice.</param> + /// <returns>A slice that consists of items from the <paramref name="start" /> index to, but not including, the <paramref name="end" /> sequence position in the current read-only sequence.</returns> public ReadOnlySequence<T> Slice(int start, SequencePosition end) => Slice((long)start, end); /// <summary> - /// Forms a slice out of the given <see cref="ReadOnlySequence{T}"/>, beginning at '<paramref name="start"/>, with <paramref name="length"/> items + /// Forms a slice out of the current <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, with <paramref name="length"/> items. /// </summary> /// <param name="start">The starting (inclusive) <see cref="SequencePosition"/> at which to begin this slice.</param> - /// <param name="length">The length of the slice</param> + /// <param name="length">The length of the slice.</param> + /// <returns>A slice that consists of <paramref name="length" /> elements from the current instance starting at sequence position <paramref name="start" />.</returns> public ReadOnlySequence<T> Slice(SequencePosition start, int length) => Slice(start, (long)length); /// <summary> - /// Forms a slice out of the given <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, ending at <paramref name="end"/> (inclusive). + /// Forms a slice out of the given <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, ending at <paramref name="end"/> (exclusive). /// </summary> /// <param name="start">The starting (inclusive) <see cref="SequencePosition"/> at which to begin this slice.</param> - /// <param name="end">The ending (inclusive) <see cref="SequencePosition"/> of the slice</param> + /// <param name="end">The ending (exclusive) <see cref="SequencePosition"/> of the slice.</param> + /// <returns>A slice that consists of items from the <paramref name="start" /> sequence position to, but not including, the <paramref name="end" /> sequence position in the current read-only sequence.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySequence<T> Slice(SequencePosition start, SequencePosition end) { @@ -413,20 +461,23 @@ namespace System.Buffers } /// <summary> - /// Forms a slice out of the given <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, ending at the existing <see cref="ReadOnlySequence{T}"/>'s end. + /// Forms a slice out of the current <see cref="ReadOnlySequence{T}" />, beginning at a specified sequence position and continuing to the end of the read-only sequence. /// </summary> /// <param name="start">The starting (inclusive) <see cref="SequencePosition"/> at which to begin this slice.</param> + /// <returns>A slice starting at sequence position <paramref name="start" /> and continuing to the end of the current read-only sequence.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySequence<T> Slice(SequencePosition start) { - BoundsCheck(start); - return SliceImpl(start, _sequenceEnd); + bool positionIsNotNull = start.GetObject() != null; + BoundsCheck(start, positionIsNotNull); + return SliceImpl(positionIsNotNull ? start : Start); } /// <summary> - /// Forms a slice out of the given <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, ending at the existing <see cref="ReadOnlySequence{T}"/>'s end. + /// Forms a slice out of the current <see cref="ReadOnlySequence{T}" /> , beginning at a specified index and continuing to the end of the read-only sequence. /// </summary> /// <param name="start">The start index at which to begin this slice.</param> + /// <returns>A slice starting at index <paramref name="start" /> and continuing to the end of the current read-only sequence.</returns> public ReadOnlySequence<T> Slice(long start) { if (start < 0) @@ -435,8 +486,8 @@ namespace System.Buffers if (start == 0) return this; - SequencePosition begin = Seek(_sequenceStart, _sequenceEnd, start, ExceptionArgument.start); - return SliceImpl(begin, _sequenceEnd); + SequencePosition begin = Seek(start, ExceptionArgument.start); + return SliceImpl(begin); } /// <inheritdoc /> @@ -454,19 +505,7 @@ namespace System.Buffers if (Length < int.MaxValue) { -#if !FEATURE_PORTABLE_SPAN - return string.Create((int)Length, charSequence, (span, sequence) => - { - foreach (ReadOnlyMemory<char> readOnlyMemory in sequence) - { - ReadOnlySpan<char> sourceSpan = readOnlyMemory.Span; - sourceSpan.CopyTo(span); - span = span.Slice(sourceSpan.Length); - } - }); -#else - return new string(charSequence.ToArray()); -#endif + return string.Create((int)Length, charSequence, (span, sequence) => sequence.CopyTo(span)); } } @@ -481,7 +520,13 @@ namespace System.Buffers /// <summary> /// Returns a new <see cref="SequencePosition"/> at an <paramref name="offset"/> from the start of the sequence. /// </summary> - public SequencePosition GetPosition(long offset) => GetPosition(offset, _sequenceStart); + public SequencePosition GetPosition(long offset) + { + if (offset < 0) + ThrowHelper.ThrowArgumentOutOfRangeException_OffsetOutOfRange(); + + return Seek(offset); + } /// <summary> /// Returns a new <see cref="SequencePosition"/> at an <paramref name="offset"/> from the <paramref name="origin"/> @@ -491,7 +536,7 @@ namespace System.Buffers if (offset < 0) ThrowHelper.ThrowArgumentOutOfRangeException_OffsetOutOfRange(); - return Seek(origin, _sequenceEnd, offset, ExceptionArgument.offset); + return Seek(origin, offset); } /// <summary> @@ -592,4 +637,4 @@ namespace System.Buffers [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int StringToSequenceEnd(int endIndex) => endIndex | StringEndMask; } -} +}
\ No newline at end of file diff --git a/src/System.Memory/src/System/Buffers/ReadOnlySequence_helpers.cs b/src/System.Memory/src/System/Buffers/ReadOnlySequence_helpers.cs index a8ea4a232b..21b7826ee2 100644 --- a/src/System.Memory/src/System/Buffers/ReadOnlySequence_helpers.cs +++ b/src/System.Memory/src/System/Buffers/ReadOnlySequence_helpers.cs @@ -4,10 +4,8 @@ using System.Diagnostics; using System.Runtime.CompilerServices; - -#if !netstandard +using System.Runtime.InteropServices; using Internal.Runtime.CompilerServices; -#endif namespace System.Buffers { @@ -26,9 +24,9 @@ namespace System.Buffers } SequenceType type = GetSequenceType(); - object endObject = _sequenceEnd.GetObject(); + object endObject = _endObject; int startIndex = GetIndex(position); - int endIndex = GetIndex(_sequenceEnd); + int endIndex = GetIndex(_endInteger); if (type == SequenceType.MultiSegment) { @@ -83,15 +81,15 @@ namespace System.Buffers [MethodImpl(MethodImplOptions.AggressiveInlining)] private ReadOnlyMemory<T> GetFirstBuffer() { - object startObject = _sequenceStart.GetObject(); + object startObject = _startObject; if (startObject == null) return default; - int startIndex = _sequenceStart.GetInteger(); - int endIndex = _sequenceEnd.GetInteger(); + int startIndex = _startInteger; + int endIndex = _endInteger; - bool isMultiSegment = startObject != _sequenceEnd.GetObject(); + bool isMultiSegment = startObject != _endObject; // The highest bit of startIndex and endIndex are used to infer the sequence type // The code below is structured this way for performance reasons and is equivalent to the following: @@ -104,59 +102,179 @@ namespace System.Buffers // Highest bit of startIndex: A = startIndex >> 31 // Highest bit of endIndex: B = endIndex >> 31 - if (startIndex >= 0) + // A == 0 && B == 0 means SequenceType.MultiSegment + // Equivalent to startIndex >= 0 && endIndex >= 0 + if ((startIndex | endIndex) >= 0) + { + ReadOnlyMemory<T> memory = ((ReadOnlySequenceSegment<T>)startObject).Memory; + if (isMultiSegment) + { + return memory.Slice(startIndex); + } + return memory.Slice(startIndex, endIndex - startIndex); + } + else { - // A == 0 && B == 0 means SequenceType.MultiSegment - // A == 0 && B == 1 means SequenceType.Array + return GetFirstBufferSlow(startObject, isMultiSegment); + } + } - if (endIndex >= 0) // SequenceType.MultiSegment + [MethodImpl(MethodImplOptions.NoInlining)] + private ReadOnlyMemory<T> GetFirstBufferSlow(object startObject, bool isMultiSegment) + { + if (isMultiSegment) + ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached(); + + int startIndex = _startInteger; + int endIndex = _endInteger; + + Debug.Assert(startIndex < 0 || endIndex < 0); + + // A == 0 && B == 1 means SequenceType.Array + if (startIndex >= 0) + { + Debug.Assert(endIndex < 0); + return new ReadOnlyMemory<T>((T[])startObject, startIndex, (endIndex & ReadOnlySequence.IndexBitMask) - startIndex); + } + else + { + // The type == char check here is redundant. However, we still have it to allow + // the JIT to see when that the code is unreachable and eliminate it. + // A == 1 && B == 1 means SequenceType.String + if (typeof(T) == typeof(char) && endIndex < 0) { - ReadOnlyMemory<T> memory = ((ReadOnlySequenceSegment<T>)startObject).Memory; - if (isMultiSegment) - { - return memory.Slice(startIndex); - } - return memory.Slice(startIndex, endIndex - startIndex); + // No need to remove the FlagBitMask since (endIndex - startIndex) == (endIndex & ReadOnlySequence.IndexBitMask) - (startIndex & ReadOnlySequence.IndexBitMask) + return (ReadOnlyMemory<T>)(object)((string)startObject).AsMemory(startIndex & ReadOnlySequence.IndexBitMask, endIndex - startIndex); } - else // endIndex < 0, SequenceType.Array + else // endIndex >= 0, A == 1 && B == 0 means SequenceType.MemoryManager { - if (isMultiSegment) - ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached(); - - return new ReadOnlyMemory<T>((T[])startObject, startIndex, (endIndex & ReadOnlySequence.IndexBitMask) - startIndex); + startIndex &= ReadOnlySequence.IndexBitMask; + return ((MemoryManager<T>)startObject).Memory.Slice(startIndex, endIndex - startIndex); } } - else // startIndex < 0 + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ReadOnlySpan<T> GetFirstSpan() + { + object startObject = _startObject; + + if (startObject == null) + return default; + + int startIndex = _startInteger; + int endIndex = _endInteger; + + bool isMultiSegment = startObject != _endObject; + + // The highest bit of startIndex and endIndex are used to infer the sequence type + // The code below is structured this way for performance reasons and is equivalent to the following: + // SequenceType type = GetSequenceType(); + // if (type == SequenceType.MultiSegment) { ... } + // else if (type == SequenceType.Array) { ... } + // else if (type == SequenceType.String){ ... } + // else if (type == SequenceType.MemoryManager) { ... } + + // Highest bit of startIndex: A = startIndex >> 31 + // Highest bit of endIndex: B = endIndex >> 31 + + // A == 0 && B == 0 means SequenceType.MultiSegment + // Equivalent to startIndex >= 0 && endIndex >= 0 + if ((startIndex | endIndex) >= 0) { + ReadOnlySpan<T> span = ((ReadOnlySequenceSegment<T>)startObject).Memory.Span; if (isMultiSegment) - ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached(); + { + return span.Slice(startIndex); + } + return span.Slice(startIndex, endIndex - startIndex); + } + else + { + return GetFirstSpanSlow(startObject, isMultiSegment); + } + } - // A == 1 && B == 1 means SequenceType.String - // A == 1 && B == 0 means SequenceType.MemoryManager + [MethodImpl(MethodImplOptions.NoInlining)] + private ReadOnlySpan<T> GetFirstSpanSlow(object startObject, bool isMultiSegment) + { + if (isMultiSegment) + ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached(); + + int startIndex = _startInteger; + int endIndex = _endInteger; + Debug.Assert(startIndex < 0 || endIndex < 0); + + // A == 0 && B == 1 means SequenceType.Array + if (startIndex >= 0) + { + Debug.Assert(endIndex < 0); + ReadOnlySpan<T> span = (T[])startObject; + return span.Slice(startIndex, (endIndex & ReadOnlySequence.IndexBitMask) - startIndex); + } + else + { // The type == char check here is redundant. However, we still have it to allow // the JIT to see when that the code is unreachable and eliminate it. - if (typeof(T) == typeof(char) && endIndex < 0) // SequenceType.String + // A == 1 && B == 1 means SequenceType.String + if (typeof(T) == typeof(char) && endIndex < 0) { + var memory = (ReadOnlyMemory<T>)(object)((string)startObject).AsMemory(); // No need to remove the FlagBitMask since (endIndex - startIndex) == (endIndex & ReadOnlySequence.IndexBitMask) - (startIndex & ReadOnlySequence.IndexBitMask) - return (ReadOnlyMemory<T>)(object)((string)startObject).AsMemory((startIndex & ReadOnlySequence.IndexBitMask), endIndex - startIndex); + return memory.Span.Slice(startIndex & ReadOnlySequence.IndexBitMask, endIndex - startIndex); } - else // endIndex >= 0, SequenceType.MemoryManager + else // endIndex >= 0, A == 1 && B == 0 means SequenceType.MemoryManager { startIndex &= ReadOnlySequence.IndexBitMask; - return ((MemoryManager<T>)startObject).Memory.Slice(startIndex, endIndex - startIndex); + return ((MemoryManager<T>)startObject).Memory.Span.Slice(startIndex, endIndex - startIndex); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private SequencePosition Seek(in SequencePosition start, in SequencePosition end, long offset, ExceptionArgument argument) + internal SequencePosition Seek(long offset, ExceptionArgument exceptionArgument = ExceptionArgument.offset) { - int startIndex = GetIndex(start); - int endIndex = GetIndex(end); + object startObject = _startObject; + object endObject = _endObject; + int startIndex = GetIndex(_startInteger); + int endIndex = GetIndex(_endInteger); + + if (startObject != endObject) + { + Debug.Assert(startObject != null); + var startSegment = (ReadOnlySequenceSegment<T>)startObject; + + int currentLength = startSegment.Memory.Length - startIndex; + // Position in start segment, defer to single segment seek + if (currentLength > offset) + goto IsSingleSegment; + + if (currentLength < 0) + ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange(); + + // End of segment. Move to start of next. + return SeekMultiSegment(startSegment.Next!, endObject!, endIndex, offset - currentLength, exceptionArgument); + } + + Debug.Assert(startObject == endObject); + + if (endIndex - startIndex < offset) + ThrowHelper.ThrowArgumentOutOfRangeException(exceptionArgument); + + // Single segment Seek + IsSingleSegment: + return new SequencePosition(startObject, startIndex + (int)offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private SequencePosition Seek(in SequencePosition start, long offset) + { object startObject = start.GetObject(); - object endObject = end.GetObject(); + object endObject = _endObject; + int startIndex = GetIndex(start); + int endIndex = GetIndex(_endInteger); if (startObject != endObject) { @@ -173,13 +291,13 @@ namespace System.Buffers ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange(); // End of segment. Move to start of next. - return SeekMultiSegment(startSegment.Next, endObject, endIndex, offset - currentLength, argument); + return SeekMultiSegment(startSegment.Next!, endObject!, endIndex, offset - currentLength, ExceptionArgument.offset); } Debug.Assert(startObject == endObject); if (endIndex - startIndex < offset) - ThrowHelper.ThrowArgumentOutOfRangeException(argument); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.offset); // Single segment Seek IsSingleSegment: @@ -189,7 +307,7 @@ namespace System.Buffers [MethodImpl(MethodImplOptions.NoInlining)] private static SequencePosition SeekMultiSegment(ReadOnlySequenceSegment<T> currentSegment, object endObject, int endIndex, long offset, ExceptionArgument argument) { - Debug.Assert(currentSegment != null); + Debug.Assert(currentSegment != null); // currentSegment parameter is marked as nullable as the parameter is reused/reassigned in the body Debug.Assert(offset >= 0); while (currentSegment != null && currentSegment != endObject) @@ -213,14 +331,15 @@ namespace System.Buffers return new SequencePosition(currentSegment, (int)offset); } - private void BoundsCheck(in SequencePosition position) + private void BoundsCheck(in SequencePosition position, bool positionIsNotNull) { uint sliceStartIndex = (uint)GetIndex(position); - uint startIndex = (uint)GetIndex(_sequenceStart); - uint endIndex = (uint)GetIndex(_sequenceEnd); - object startObject = _sequenceStart.GetObject(); - object endObject = _sequenceEnd.GetObject(); + object startObject = _startObject; + object endObject = _endObject; + + uint startIndex = (uint)GetIndex(_startInteger); + uint endIndex = (uint)GetIndex(_endInteger); // Single-Segment Sequence if (startObject == endObject) @@ -234,11 +353,18 @@ namespace System.Buffers { // Multi-Segment Sequence // Storing this in a local since it is used twice within InRange() - ulong startRange = (ulong)(((ReadOnlySequenceSegment<T>)startObject).RunningIndex + startIndex); + ulong startRange = (ulong)(((ReadOnlySequenceSegment<T>)startObject!).RunningIndex + startIndex); + long runningIndex = 0; + if (positionIsNotNull) + { + Debug.Assert(position.GetObject() != null); + runningIndex = ((ReadOnlySequenceSegment<T>)position.GetObject()!).RunningIndex; + } + if (!InRange( - (ulong)(((ReadOnlySequenceSegment<T>)position.GetObject()).RunningIndex + sliceStartIndex), + (ulong)(runningIndex + sliceStartIndex), startRange, - (ulong)(((ReadOnlySequenceSegment<T>)endObject).RunningIndex + endIndex))) + (ulong)(((ReadOnlySequenceSegment<T>)endObject!).RunningIndex + endIndex))) { ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange(); } @@ -247,11 +373,11 @@ namespace System.Buffers private void BoundsCheck(uint sliceStartIndex, object sliceStartObject, uint sliceEndIndex, object sliceEndObject) { - uint startIndex = (uint)GetIndex(_sequenceStart); - uint endIndex = (uint)GetIndex(_sequenceEnd); + object startObject = _startObject; + object endObject = _endObject; - object startObject = _sequenceStart.GetObject(); - object endObject = _sequenceEnd.GetObject(); + uint startIndex = (uint)GetIndex(_startInteger); + uint endIndex = (uint)GetIndex(_endInteger); // Single-Segment Sequence if (startObject == endObject) @@ -271,14 +397,24 @@ namespace System.Buffers // This optimization works because we know sliceStartIndex, sliceEndIndex, startIndex, and endIndex are all >= 0 Debug.Assert(sliceStartIndex >= 0 && startIndex >= 0 && endIndex >= 0); - ulong sliceStartRange = (ulong)(((ReadOnlySequenceSegment<T>)sliceStartObject).RunningIndex + sliceStartIndex); - ulong sliceEndRange = (ulong)(((ReadOnlySequenceSegment<T>)sliceEndObject).RunningIndex + sliceEndIndex); + ulong sliceStartRange = sliceStartIndex; + ulong sliceEndRange = sliceEndIndex; + + if (sliceStartObject != null) + { + sliceStartRange += (ulong)((ReadOnlySequenceSegment<T>)sliceStartObject).RunningIndex; + } + + if (sliceEndObject != null) + { + sliceEndRange += (ulong)((ReadOnlySequenceSegment<T>)sliceEndObject).RunningIndex; + } if (sliceStartRange > sliceEndRange) ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange(); - if (sliceStartRange < (ulong)(((ReadOnlySequenceSegment<T>)startObject).RunningIndex + startIndex) - || sliceEndRange > (ulong)(((ReadOnlySequenceSegment<T>)endObject).RunningIndex + endIndex)) + if (sliceStartRange < (ulong)(((ReadOnlySequenceSegment<T>)startObject!).RunningIndex + startIndex) + || sliceEndRange > (ulong)(((ReadOnlySequenceSegment<T>)endObject!).RunningIndex + endIndex)) { ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange(); } @@ -322,13 +458,16 @@ namespace System.Buffers // start >> 31 = 0, end >> 31 = -1 // 2 * 0 + (-1) = -1, result = (SequenceType)1 - return (SequenceType)(-(2 * (_sequenceStart.GetInteger() >> 31) + (_sequenceEnd.GetInteger() >> 31))); + return (SequenceType)(-(2 * (_startInteger >> 31) + (_endInteger >> 31))); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetIndex(in SequencePosition position) => position.GetInteger() & ReadOnlySequence.IndexBitMask; [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetIndex(int Integer) => Integer & ReadOnlySequence.IndexBitMask; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] private ReadOnlySequence<T> SliceImpl(in SequencePosition start, in SequencePosition end) { // In this method we reset high order bits from indices @@ -337,24 +476,39 @@ namespace System.Buffers return new ReadOnlySequence<T>( start.GetObject(), - GetIndex(start) | (_sequenceStart.GetInteger() & ReadOnlySequence.FlagBitMask), + GetIndex(start) | (_startInteger & ReadOnlySequence.FlagBitMask), end.GetObject(), - GetIndex(end) | (_sequenceEnd.GetInteger() & ReadOnlySequence.FlagBitMask) + GetIndex(end) | (_endInteger & ReadOnlySequence.FlagBitMask) + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ReadOnlySequence<T> SliceImpl(in SequencePosition start) + { + // In this method we reset high order bits from indices + // of positions that were passed in + // and apply type bits specific for current ReadOnlySequence type + + return new ReadOnlySequence<T>( + start.GetObject(), + GetIndex(start) | (_startInteger & ReadOnlySequence.FlagBitMask), + _endObject, + _endInteger ); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private long GetLength() { - int startIndex = GetIndex(_sequenceStart); - int endIndex = GetIndex(_sequenceEnd); - object startObject = _sequenceStart.GetObject(); - object endObject = _sequenceEnd.GetObject(); + object startObject = _startObject; + object endObject = _endObject; + int startIndex = GetIndex(_startInteger); + int endIndex = GetIndex(_endInteger); if (startObject != endObject) { - var startSegment = (ReadOnlySequenceSegment<T>)startObject; - var endSegment = (ReadOnlySequenceSegment<T>)endObject; + var startSegment = (ReadOnlySequenceSegment<T>)startObject!; + var endSegment = (ReadOnlySequenceSegment<T>)endObject!; // (End offset) - (start offset) return (endSegment.RunningIndex + endIndex) - (startSegment.RunningIndex + startIndex); } @@ -365,7 +519,7 @@ namespace System.Buffers internal bool TryGetReadOnlySequenceSegment(out ReadOnlySequenceSegment<T> startSegment, out int startIndex, out ReadOnlySequenceSegment<T> endSegment, out int endIndex) { - object startObject = _sequenceStart.GetObject(); + object startObject = _startObject; // Default or not MultiSegment if (startObject == null || GetSequenceType() != SequenceType.MultiSegment) @@ -376,13 +530,13 @@ namespace System.Buffers endIndex = 0; return false; } - - Debug.Assert(_sequenceEnd.GetObject() != null); + + Debug.Assert(_endObject != null); startSegment = (ReadOnlySequenceSegment<T>)startObject; - startIndex = GetIndex(_sequenceStart); - endSegment = (ReadOnlySequenceSegment<T>)_sequenceEnd.GetObject(); - endIndex = GetIndex(_sequenceEnd); + startIndex = GetIndex(_startInteger); + endSegment = (ReadOnlySequenceSegment<T>)_endObject; + endIndex = GetIndex(_endInteger); return true; } @@ -394,10 +548,10 @@ namespace System.Buffers return false; } - Debug.Assert(_sequenceStart.GetObject() != null); + Debug.Assert(_startObject != null); - int startIndex = GetIndex(_sequenceStart); - segment = new ArraySegment<T>((T[])_sequenceStart.GetObject(), startIndex, GetIndex(_sequenceEnd) - startIndex); + int startIndex = GetIndex(_startInteger); + segment = new ArraySegment<T>((T[])_startObject, startIndex, GetIndex(_endInteger) - startIndex); return true; } @@ -411,11 +565,11 @@ namespace System.Buffers return false; } - Debug.Assert(_sequenceStart.GetObject() != null); + Debug.Assert(_startObject != null); - start = GetIndex(_sequenceStart); - length = GetIndex(_sequenceEnd) - start; - text = (string)_sequenceStart.GetObject(); + start = GetIndex(_startInteger); + length = GetIndex(_endInteger) - start; + text = (string)_startObject; return true; } @@ -432,7 +586,7 @@ namespace System.Buffers // The case, value < start, is invalid. // In that case, (value - start) would underflow becoming larger than int.MaxValue. - // (end - start) can never underflow and hence must be within 0 and int.MaxValue. + // (end - start) can never underflow and hence must be within 0 and int.MaxValue. // So, we will correctly return false. // The case, value > end, is invalid. @@ -441,7 +595,7 @@ namespace System.Buffers // In all other cases, value is valid, and we return true. - // Equivalent to: return (start <= value && value <= start) + // Equivalent to: return (start <= value && value <= end) return (value - start) <= (end - start); } @@ -458,7 +612,7 @@ namespace System.Buffers // The case, value < start, is invalid. // In that case, (value - start) would underflow becoming larger than long.MaxValue. - // (end - start) can never underflow and hence must be within 0 and long.MaxValue. + // (end - start) can never underflow and hence must be within 0 and long.MaxValue. // So, we will correctly return false. // The case, value > end, is invalid. @@ -470,5 +624,79 @@ namespace System.Buffers // Equivalent to: return (start <= value && value <= start) return (value - start) <= (end - start); } + + /// <summary> + /// Helper to efficiently prepare the <see cref="SequenceReader{T}"/> + /// </summary> + /// <param name="first">The first span in the sequence.</param> + /// <param name="next">The next position.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void GetFirstSpan(out ReadOnlySpan<T> first, out SequencePosition next) + { + first = default; + next = default; + object startObject = _startObject; + int startIndex = _startInteger; + + if (startObject != null) + { + bool hasMultipleSegments = startObject != _endObject; + int endIndex = _endInteger; + + if (startIndex >= 0) + { + if (endIndex >= 0) + { + // Positive start and end index == ReadOnlySequenceSegment<T> + ReadOnlySequenceSegment<T> segment = (ReadOnlySequenceSegment<T>)startObject; + next = new SequencePosition(segment.Next, 0); + first = segment.Memory.Span; + if (hasMultipleSegments) + { + first = first.Slice(startIndex); + } + else + { + first = first.Slice(startIndex, endIndex - startIndex); + } + } + else + { + // Positive start and negative end index == T[] + if (hasMultipleSegments) + ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached(); + + first = new ReadOnlySpan<T>((T[])startObject, startIndex, (endIndex & ReadOnlySequence.IndexBitMask) - startIndex); + } + } + else + { + first = GetFirstSpanSlow(startObject, startIndex, endIndex, hasMultipleSegments); + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ReadOnlySpan<T> GetFirstSpanSlow(object startObject, int startIndex, int endIndex, bool hasMultipleSegments) + { + Debug.Assert(startIndex < 0); + if (hasMultipleSegments) + ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached(); + + // The type == char check here is redundant. However, we still have it to allow + // the JIT to see when that the code is unreachable and eliminate it. + if (typeof(T) == typeof(char) && endIndex < 0) + { + // Negative start and negative end index == string + ReadOnlySpan<char> spanOfChar = ((string)startObject).AsSpan(startIndex & ReadOnlySequence.IndexBitMask, endIndex - startIndex); + return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<char, T>(ref MemoryMarshal.GetReference(spanOfChar)), spanOfChar.Length); + } + else + { + // Negative start and positive end index == MemoryManager<T> + startIndex &= ReadOnlySequence.IndexBitMask; + return ((MemoryManager<T>)startObject).Memory.Span.Slice(startIndex, endIndex - startIndex); + } + } } -} +}
\ No newline at end of file diff --git a/src/System.Memory/src/System/Buffers/StandardFormat.cs b/src/System.Memory/src/System/Buffers/StandardFormat.cs index 6975c204f1..83014d06b0 100644 --- a/src/System.Memory/src/System/Buffers/StandardFormat.cs +++ b/src/System.Memory/src/System/Buffers/StandardFormat.cs @@ -67,12 +67,34 @@ namespace System.Buffers public static implicit operator StandardFormat(char symbol) => new StandardFormat(symbol); /// <summary> - /// Converts a classic .NET format string into a StandardFormat + /// Converts a <see cref="ReadOnlySpan{Char}"/> into a StandardFormat /// </summary> public static StandardFormat Parse(ReadOnlySpan<char> format) { + ParseHelper(format, out StandardFormat standardFormat, throws: true); + + return standardFormat; + } + + /// <summary> + /// Converts a classic .NET format string into a StandardFormat + /// </summary> + public static StandardFormat Parse(string format) => format == null ? default : Parse(format.AsSpan()); + + /// <summary> + /// Tries to convert a <see cref="ReadOnlySpan{Char}"/> into a StandardFormat. A return value indicates whether the conversion succeeded or failed. + /// </summary> + public static bool TryParse(ReadOnlySpan<char> format, out StandardFormat result) + { + return ParseHelper(format, out result); + } + + private static bool ParseHelper(ReadOnlySpan<char> format, out StandardFormat standardFormat, bool throws = false) + { + standardFormat = default; + if (format.Length == 0) - return default; + return true; char symbol = format[0]; byte precision; @@ -87,25 +109,24 @@ namespace System.Buffers { uint digit = format[srcIndex] - 48u; // '0' if (digit > 9) - throw new FormatException(SR.Format(SR.Argument_CannotParsePrecision, MaxPrecision)); - + { + return throws ? throw new FormatException(SR.Format(SR.Argument_CannotParsePrecision, MaxPrecision)) : false; + } parsedPrecision = parsedPrecision * 10 + digit; if (parsedPrecision > MaxPrecision) - throw new FormatException(SR.Format(SR.Argument_PrecisionTooLarge, MaxPrecision)); + { + return throws ? throw new FormatException(SR.Format(SR.Argument_PrecisionTooLarge, MaxPrecision)) : false; + } } precision = (byte)parsedPrecision; } - return new StandardFormat(symbol, precision); + standardFormat = new StandardFormat(symbol, precision); + return true; } /// <summary> - /// Converts a classic .NET format string into a StandardFormat - /// </summary> - public static StandardFormat Parse(string format) => format == null ? default : Parse(format.AsSpan()); - - /// <summary> /// Returns true if both the Symbol and Precision are equal. /// </summary> public override bool Equals(object obj) => obj is StandardFormat other && Equals(other); @@ -125,40 +146,54 @@ namespace System.Buffers /// </summary> public override string ToString() { - unsafe + Span<char> buffer = stackalloc char[FormatStringLength]; + int charsWritten = Format(buffer); + return new string(buffer.Slice(0, charsWritten)); + } + + /// <summary>The exact buffer length required by <see cref="Format"/>.</summary> + internal const int FormatStringLength = 3; + + /// <summary> + /// Formats the format in classic .NET format. + /// </summary> + internal int Format(Span<char> destination) + { + Debug.Assert(destination.Length == FormatStringLength); + + int count = 0; + char symbol = Symbol; + + if (symbol != default && + (uint)destination.Length == FormatStringLength) // to eliminate bounds checks { - const int MaxLength = 4; - char* pBuffer = stackalloc char[MaxLength]; + destination[0] = symbol; + count = 1; - int dstIndex = 0; - char symbol = Symbol; - if (symbol != default) + uint precision = Precision; + if (precision != NoPrecision) { - pBuffer[dstIndex++] = symbol; - - byte precision = Precision; - if (precision != NoPrecision) + // Note that Precision is stored as a byte, so in theory it could contain + // values > MaxPrecision (99). But all supported mechanisms for creating a + // StandardFormat limit values to being <= MaxPrecision, so the only way a value + // could be larger than that is if unsafe code or the equivalent were used + // to force a larger invalid value in, in which case we don't need to + // guarantee such an invalid value is properly roundtripped through here; + // we just need to make sure things aren't corrupted further. + + if (precision >= 10) { - if (precision >= 100) - { - pBuffer[dstIndex++] = (char)('0' + (precision / 100) % 10); - precision = (byte)(precision % 100); - } - - if (precision >= 10) - { - pBuffer[dstIndex++] = (char)('0' + (precision / 10) % 10); - precision = (byte)(precision % 10); - } - - pBuffer[dstIndex++] = (char)('0' + precision); + uint div = Math.DivRem(precision, 10, out precision); + destination[1] = (char)('0' + div % 10); + count = 2; } - } - - Debug.Assert(dstIndex <= MaxLength); - return new string(pBuffer, startIndex: 0, length: dstIndex); + destination[count] = (char)('0' + precision); + count++; + } } + + return count; } /// <summary> diff --git a/src/System.Memory/src/System/Runtime/InteropServices/SequenceMarshal.cs b/src/System.Memory/src/System/Runtime/InteropServices/SequenceMarshal.cs index 8335a4144b..ded0b44d9d 100644 --- a/src/System.Memory/src/System/Runtime/InteropServices/SequenceMarshal.cs +++ b/src/System.Memory/src/System/Runtime/InteropServices/SequenceMarshal.cs @@ -57,5 +57,22 @@ namespace System.Runtime.InteropServices { return sequence.TryGetString(out text, out start, out length); } + + /// <summary> + /// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary + /// structs- see remarks for full details. + /// </summary> + /// <remarks> + /// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to + /// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit + /// overloads such as <see cref="SequenceReaderExtensions.TryReadLittleEndian(ref SequenceReader{byte}, out int)"/> + /// </remarks> + /// <returns> + /// True if successful. <paramref name="value"/> will be default if failed (due to lack of space). + /// </returns> + public static bool TryRead<T>(ref SequenceReader<byte> reader, out T value) where T : unmanaged + { + return reader.TryRead<T>(out value); + } } } |