Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ public readonly partial struct ReadOnlySequence<T>
public ReadOnlySequence(T[] array, int start, int length) { throw null; }
public System.SequencePosition End { get { throw null; } }
public System.ReadOnlyMemory<T> First { get { throw null; } }
public System.ReadOnlySpan<T> FirstSpan { get { throw null; } }
public bool IsEmpty { get { throw null; } }
public bool IsSingleSegment { get { throw null; } }
public long Length { get { throw null; } }
Expand Down
78 changes: 78 additions & 0 deletions src/System.Memory/src/System/Buffers/ReadOnlySequence.Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,84 @@ private ReadOnlyMemory<T> GetFirstBufferSlow(object startObject, bool isMultiSeg
}
}

[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;

Comment thread
maryamariyan marked this conversation as resolved.
// 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)
{
return span.Slice(startIndex);
}
return span.Slice(startIndex, endIndex - startIndex);
}
else
{
return GetFirstSpanSlow(startObject, isMultiSegment);
}
}

[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.
// 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 memory.Span.Slice(startIndex & ReadOnlySequence.IndexBitMask, endIndex - startIndex);
}
else // endIndex >= 0, A == 1 && B == 0 means SequenceType.MemoryManager
{
startIndex &= ReadOnlySequence.IndexBitMask;
return ((MemoryManager<T>)startObject).Memory.Span.Slice(startIndex, endIndex - startIndex);
}
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal SequencePosition Seek(long offset, ExceptionArgument exceptionArgument = ExceptionArgument.offset)
{
Expand Down
5 changes: 5 additions & 0 deletions src/System.Memory/src/System/Buffers/ReadOnlySequence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public bool IsSingleSegment
/// </summary>
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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<char>(SampleString.AsMemory());
VerifyCanGetFirst(buffer, expectedSize: 5);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.MemoryTests;
using System.Text;
using Xunit;

Expand Down Expand Up @@ -109,41 +110,65 @@ public void CheckEndReachableDoesNotCrossPastEnd()
#region First

[Fact]
public void CanGetFirst()
public void AsArray_CanGetFirst()
{
var memory = new ReadOnlyMemory<T>(new T[5]);
VerifyCanGetFirst(new ReadOnlySequence<T>(memory), expectedSize: 5);
}

[Fact]
public void AsMemoryManager_CanGetFirst()
{
MemoryManager<T> manager = new CustomMemoryForTest<T>(new T[5]);
ReadOnlyMemory<T> memoryFromManager = ((ReadOnlyMemory<T>)manager.Memory);

VerifyCanGetFirst(new ReadOnlySequence<T>(memoryFromManager), expectedSize: 5);
}

[Fact]
public void AsMultiSegment_CanGetFirst()
Comment thread
maryamariyan marked this conversation as resolved.
{
var bufferSegment1 = new BufferSegment<T>(new T[100]);
BufferSegment<T> bufferSegment2 = bufferSegment1.Append(new T[100]);
BufferSegment<T> bufferSegment3 = bufferSegment2.Append(new T[100]);
BufferSegment<T> bufferSegment4 = bufferSegment3.Append(new T[200]);

var buffer = new ReadOnlySequence<T>(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<T> 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);
}

Assert.Equal(0, buffer.Length);
Assert.Equal(0, buffer.First.Length);
Assert.Equal(0, buffer.FirstSpan.Length);
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

Expand Down