diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index e0a05cbabd0a..46af74f20413 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -160,6 +160,7 @@ public readonly partial struct ReadOnlySequence public ReadOnlySequence(T[] array, int start, int length) { throw null; } public System.SequencePosition End { get { throw null; } } public System.ReadOnlyMemory First { get { throw null; } } + public System.ReadOnlySpan FirstSpan { get { throw null; } } public bool IsEmpty { get { throw null; } } public bool IsSingleSegment { get { throw null; } } public long Length { get { throw null; } } diff --git a/src/System.Memory/src/System/Buffers/ReadOnlySequence.Helpers.cs b/src/System.Memory/src/System/Buffers/ReadOnlySequence.Helpers.cs index 8c396df232f3..4612016a0b43 100644 --- a/src/System.Memory/src/System/Buffers/ReadOnlySequence.Helpers.cs +++ b/src/System.Memory/src/System/Buffers/ReadOnlySequence.Helpers.cs @@ -154,6 +154,84 @@ private ReadOnlyMemory GetFirstBufferSlow(object startObject, bool isMultiSeg } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ReadOnlySpan 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 span = ((ReadOnlySequenceSegment)startObject).Memory.Span; + if (isMultiSegment) + { + return span.Slice(startIndex); + } + return span.Slice(startIndex, endIndex - startIndex); + } + else + { + return GetFirstSpanSlow(startObject, isMultiSegment); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private ReadOnlySpan 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 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. + // A == 1 && B == 1 means SequenceType.String + if (typeof(T) == typeof(char) && endIndex < 0) + { + var memory = (ReadOnlyMemory)(object)((string)startObject).AsMemory(); + // No need to remove the FlagBitMask since (endIndex - startIndex) == (endIndex & ReadOnlySequence.IndexBitMask) - (startIndex & ReadOnlySequence.IndexBitMask) + return memory.Span.Slice(startIndex & ReadOnlySequence.IndexBitMask, endIndex - startIndex); + } + else // endIndex >= 0, A == 1 && B == 0 means SequenceType.MemoryManager + { + startIndex &= ReadOnlySequence.IndexBitMask; + return ((MemoryManager)startObject).Memory.Span.Slice(startIndex, endIndex - startIndex); + } + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal SequencePosition Seek(long offset, ExceptionArgument exceptionArgument = ExceptionArgument.offset) { diff --git a/src/System.Memory/src/System/Buffers/ReadOnlySequence.cs b/src/System.Memory/src/System/Buffers/ReadOnlySequence.cs index 319189fa23dd..1ade0921255a 100644 --- a/src/System.Memory/src/System/Buffers/ReadOnlySequence.cs +++ b/src/System.Memory/src/System/Buffers/ReadOnlySequence.cs @@ -51,6 +51,11 @@ public bool IsSingleSegment /// public ReadOnlyMemory First => GetFirstBuffer(); + /// + /// Gets from the first segment. + /// + public ReadOnlySpan FirstSpan => GetFirstSpan(); + /// /// A position to the start of the . /// diff --git a/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Common.char.cs b/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Common.char.cs index f42d140297ae..ef450f336d44 100644 --- a/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Common.char.cs +++ b/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Common.char.cs @@ -80,5 +80,13 @@ public void HelloWorldAcrossTwoBlocks() Assert.Equal("Hello", new string(helloBytes)); Assert.Equal(" World", new string(worldBytes)); } + + [Fact] + public void AsString_CanGetFirst() + { + const string SampleString = "12345"; + var buffer = new ReadOnlySequence(SampleString.AsMemory()); + VerifyCanGetFirst(buffer, expectedSize: 5); + } } } diff --git a/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Common.cs b/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Common.cs index 685388b48ee4..e274546cd362 100644 --- a/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Common.cs +++ b/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Common.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Collections.Generic; using System.Linq; +using System.MemoryTests; using System.Text; using Xunit; @@ -109,7 +110,23 @@ public void CheckEndReachableDoesNotCrossPastEnd() #region First [Fact] - public void CanGetFirst() + public void AsArray_CanGetFirst() + { + var memory = new ReadOnlyMemory(new T[5]); + VerifyCanGetFirst(new ReadOnlySequence(memory), expectedSize: 5); + } + + [Fact] + public void AsMemoryManager_CanGetFirst() + { + MemoryManager manager = new CustomMemoryForTest(new T[5]); + ReadOnlyMemory memoryFromManager = ((ReadOnlyMemory)manager.Memory); + + VerifyCanGetFirst(new ReadOnlySequence(memoryFromManager), expectedSize: 5); + } + + [Fact] + public void AsMultiSegment_CanGetFirst() { var bufferSegment1 = new BufferSegment(new T[100]); BufferSegment bufferSegment2 = bufferSegment1.Append(new T[100]); @@ -117,26 +134,33 @@ public void CanGetFirst() BufferSegment bufferSegment4 = bufferSegment3.Append(new T[200]); var buffer = new ReadOnlySequence(bufferSegment1, 0, bufferSegment4, 200); - + // Verify first 3 segments Assert.Equal(500, buffer.Length); int length = 500; - for (int s = 0; s < 3; s++) { for (int i = 100; i > 0; i--) { Assert.Equal(i, buffer.First.Length); + Assert.Equal(i, buffer.FirstSpan.Length); buffer = buffer.Slice(1); length--; Assert.Equal(length, buffer.Length); } } + // Verify last segment + VerifyCanGetFirst(buffer, expectedSize: 200); + } + + protected void VerifyCanGetFirst(ReadOnlySequence buffer, int expectedSize) + { + Assert.Equal(expectedSize, buffer.Length); + int length = expectedSize; - Assert.Equal(200, buffer.Length); - - for (int i = 200; i > 0; i--) + for (int i = length; i > 0; i--) { Assert.Equal(i, buffer.First.Length); + Assert.Equal(i, buffer.FirstSpan.Length); buffer = buffer.Slice(1); length--; Assert.Equal(length, buffer.Length); @@ -144,6 +168,7 @@ public void CanGetFirst() Assert.Equal(0, buffer.Length); Assert.Equal(0, buffer.First.Length); + Assert.Equal(0, buffer.FirstSpan.Length); } #endregion diff --git a/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Default.cs b/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Default.cs index 2a9efc393746..b873c3ffa7c3 100644 --- a/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Default.cs +++ b/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Default.cs @@ -21,6 +21,7 @@ public void Default_Constructor() Assert.True(buffer.IsSingleSegment); Assert.Equal(0, buffer.Length); Assert.True(buffer.First.IsEmpty); + Assert.True(buffer.FirstSpan.IsEmpty); Assert.Equal($"System.Buffers.ReadOnlySequence<{typeof(byte).Name}>[0]", buffer.ToString()); } diff --git a/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Empty.cs b/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Empty.cs index 962bec799e15..a4ad8f5fdb7e 100644 --- a/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Empty.cs +++ b/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Empty.cs @@ -22,6 +22,7 @@ public void Empty_Constructor() Assert.True(buffer.IsSingleSegment); Assert.Equal(0, buffer.Length); Assert.True(buffer.First.IsEmpty); + Assert.True(buffer.FirstSpan.IsEmpty); Assert.Equal($"System.Buffers.ReadOnlySequence<{typeof(byte).Name}>[0]", buffer.ToString()); }