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
5 changes: 5 additions & 0 deletions src/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -429,5 +429,10 @@ namespace System.Runtime.InteropServices
public static class MemoryMarshal
{
public static Memory<T> AsMemory<T>(ReadOnlyMemory<T> readOnlyMemory) { throw null; }

public static ref T GetReference<T>(Span<T> span) { throw null; }
public static ref readonly T GetReference<T>(ReadOnlySpan<T> span) { throw null; }

public static bool TryGetArray<T>(ReadOnlyMemory<T> readOnlyMemory, out ArraySegment<T> arraySegment) { throw null; }
}
}
3 changes: 2 additions & 1 deletion src/System.Memory/src/System/MemoryDebugView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.Diagnostics;
using System.Runtime.InteropServices;

namespace System
{
Expand All @@ -27,7 +28,7 @@ public T[] Items
// https://devdiv.visualstudio.com/DevDiv/_workitems?id=286592
get
{
if (_memory.DangerousTryGetArray(out ArraySegment<T> segment))
if (MemoryMarshal.TryGetArray(_memory, out ArraySegment<T> segment))
{
T[] array = new T[_memory.Length];
Array.Copy(segment.Array, segment.Offset, array, 0, array.Length);
Expand Down
2 changes: 1 addition & 1 deletion src/System.Memory/src/System/ReadOnlyMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public readonly struct ReadOnlyMemory<T>
private readonly int _index;
private readonly int _length;

private const int RemoveOwnedFlagBitMask = 0x7FFFFFFF;
internal const int RemoveOwnedFlagBitMask = 0x7FFFFFFF;

/// <summary>
/// Creates a new memory over the entirety of the target array.
Expand Down
1 change: 1 addition & 0 deletions src/System.Memory/src/System/ReadOnlySpan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ public T[] ToArray()
public static ReadOnlySpan<T> Empty => default(ReadOnlySpan<T>);

/// <summary>
/// This method is obsolete, use System.Runtime.InteropServices.MemoryMarshal.GetReference instead.
/// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element
/// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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.Buffers;
using System.Runtime.CompilerServices;

namespace System.Runtime.InteropServices
Expand All @@ -23,5 +24,54 @@ public static class MemoryMarshal
/// </remarks>
public static Memory<T> AsMemory<T>(ReadOnlyMemory<T> readOnlyMemory) =>
Unsafe.As<ReadOnlyMemory<T>, Memory<T>>(ref readOnlyMemory);

/// <summary>
/// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element
/// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
/// </summary>
public static ref T GetReference<T>(Span<T> span)
{
if (span.Pinnable == null)
unsafe { return ref Unsafe.AsRef<T>(span.ByteOffset.ToPointer()); }
else
return ref Unsafe.AddByteOffset<T>(ref span.Pinnable.Data, span.ByteOffset);
}

/// <summary>
/// Returns a reference to the 0th element of the ReadOnlySpan. If the Span is empty, returns a reference to the location where the 0th element
/// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
/// </summary>
public static ref readonly T GetReference<T>(ReadOnlySpan<T> span)
{
if (span.Pinnable == null)
unsafe { return ref Unsafe.AsRef<T>(span.ByteOffset.ToPointer()); }
else
return ref Unsafe.AddByteOffset<T>(ref span.Pinnable.Data, span.ByteOffset);
}

/// <summary>
/// Get an array segment from the underlying memory.
/// If unable to get the array segment, return false with a default array segment.
/// </summary>
public static bool TryGetArray<T>(ReadOnlyMemory<T> readOnlyMemory, out ArraySegment<T> arraySegment)
{
object obj = readOnlyMemory.GetObjectStartLength(out int index, out int length);
if (index < 0)
{
if (((OwnedMemory<T>)obj).TryGetArray(out var segment))
{
arraySegment = new ArraySegment<T>(segment.Array, segment.Offset + (index & ReadOnlyMemory<T>.RemoveOwnedFlagBitMask), length);
Copy link
Copy Markdown
Member

@KrzysztofCwalina KrzysztofCwalina Dec 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check that the length <= segment.Length - index?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If length is greater than segment.Count (if, let's say someone implements OwnedMemory.TryGetArray incorrectly and returns a subset of the backing memory), then the ArraySegment constructor will already throw ArgumentException.

System.ArgumentException : Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.

Regarding index, that only changes on a Slice operation, and we decrease the length accordingly whenever that happens. Therefore, I don't think an explicit check is necessary here.

return true;
}
}
else if (obj is T[] arr)
{
arraySegment = new ArraySegment<T>(arr, index, length);
return true;
}

arraySegment = default;
return false;
}
}
}
1 change: 1 addition & 0 deletions src/System.Memory/src/System/Span.cs
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ public T[] ToArray()
public static Span<T> Empty => default(Span<T>);

/// <summary>
/// This method is obsolete, use System.Runtime.InteropServices.MemoryMarshal.GetReference instead.
/// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element
/// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
/// </summary>
Expand Down
23 changes: 12 additions & 11 deletions src/System.Memory/tests/Memory/Slice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
// See the LICENSE file in the project root for more information.

using Xunit;
using System.Runtime.CompilerServices;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System.MemoryTests
{
Expand All @@ -16,13 +17,13 @@ public static void SliceWithStart()
int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 };
Memory<int> memory = new Memory<int>(a).Slice(6);
Assert.Equal(4, memory.Length);
Assert.True(Unsafe.AreSame(ref a[6], ref memory.Span.DangerousGetPinnableReference()));
Assert.True(Unsafe.AreSame(ref a[6], ref MemoryMarshal.GetReference(memory.Span)));

OwnedMemory<int> owner = new CustomMemoryForTest<int>(a);
Memory<int> memoryFromOwner = owner.Memory.Slice(6);

Assert.Equal(4, memoryFromOwner.Length);
Assert.True(Unsafe.AreSame(ref a[6], ref memoryFromOwner.Span.DangerousGetPinnableReference()));
Assert.True(Unsafe.AreSame(ref a[6], ref MemoryMarshal.GetReference(memoryFromOwner.Span)));
}

[Fact]
Expand All @@ -31,13 +32,13 @@ public static void SliceWithStartPastEnd()
int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 };
Memory<int> memory = new Memory<int>(a).Slice(a.Length);
Assert.Equal(0, memory.Length);
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref memory.Span.DangerousGetPinnableReference(), 1)));
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref MemoryMarshal.GetReference(memory.Span), 1)));

OwnedMemory<int> owner = new CustomMemoryForTest<int>(a);
Memory<int> memoryFromOwner = owner.Memory.Slice(a.Length);

Assert.Equal(0, memoryFromOwner.Length);
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref memoryFromOwner.Span.DangerousGetPinnableReference(), 1)));
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref MemoryMarshal.GetReference(memoryFromOwner.Span), 1)));
}

[Fact]
Expand All @@ -46,13 +47,13 @@ public static void SliceWithStartAndLength()
int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 };
Memory<int> memory = new Memory<int>(a).Slice(3, 5);
Assert.Equal(5, memory.Length);
Assert.True(Unsafe.AreSame(ref a[3], ref memory.Span.DangerousGetPinnableReference()));
Assert.True(Unsafe.AreSame(ref a[3], ref MemoryMarshal.GetReference(memory.Span)));

OwnedMemory<int> owner = new CustomMemoryForTest<int>(a);
Memory<int> memoryFromOwner = owner.Memory.Slice(3, 5);

Assert.Equal(5, memoryFromOwner.Length);
Assert.True(Unsafe.AreSame(ref a[3], ref memoryFromOwner.Span.DangerousGetPinnableReference()));
Assert.True(Unsafe.AreSame(ref a[3], ref MemoryMarshal.GetReference(memoryFromOwner.Span)));
}

[Fact]
Expand All @@ -61,13 +62,13 @@ public static void SliceWithStartAndLengthUpToEnd()
int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 };
Memory<int> memory = new Memory<int>(a).Slice(4, 6);
Assert.Equal(6, memory.Length);
Assert.True(Unsafe.AreSame(ref a[4], ref memory.Span.DangerousGetPinnableReference()));
Assert.True(Unsafe.AreSame(ref a[4], ref MemoryMarshal.GetReference(memory.Span)));

OwnedMemory<int> owner = new CustomMemoryForTest<int>(a);
Memory<int> memoryFromOwner = owner.Memory.Slice(4, 6);

Assert.Equal(6, memoryFromOwner.Length);
Assert.True(Unsafe.AreSame(ref a[4], ref memoryFromOwner.Span.DangerousGetPinnableReference()));
Assert.True(Unsafe.AreSame(ref a[4], ref MemoryMarshal.GetReference(memoryFromOwner.Span)));
}

[Fact]
Expand All @@ -76,13 +77,13 @@ public static void SliceWithStartAndLengthPastEnd()
int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 };
Memory<int> memory = new Memory<int>(a).Slice(a.Length, 0);
Assert.Equal(0, memory.Length);
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref memory.Span.DangerousGetPinnableReference(), 1)));
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref MemoryMarshal.GetReference(memory.Span), 1)));

OwnedMemory<int> owner = new CustomMemoryForTest<int>(a);
Memory<int> memoryFromOwner = owner.Memory.Slice(a.Length, 0);

Assert.Equal(0, memoryFromOwner.Length);
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref memoryFromOwner.Span.DangerousGetPinnableReference(), 1)));
Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref MemoryMarshal.GetReference(memoryFromOwner.Span), 1)));
}

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion src/System.Memory/tests/MemoryMarshal/AsMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ private static unsafe void AsMemory_Roundtrips_Core<T>(ReadOnlyMemory<T> readOnl
Assert.True(readOnlyMemory.Span == memory.Span);

// TryGetArray
Assert.True(readOnlyMemory.DangerousTryGetArray(out ArraySegment<T> array1) == memory.TryGetArray(out ArraySegment<T> array2));
Assert.True(MemoryMarshal.TryGetArray(readOnlyMemory, out ArraySegment<T> array1) == memory.TryGetArray(out ArraySegment<T> array2));
Assert.Same(array1.Array, array2.Array);
Assert.Equal(array1.Offset, array2.Offset);
Assert.Equal(array1.Count, array2.Count);
Expand Down
125 changes: 125 additions & 0 deletions src/System.Memory/tests/MemoryMarshal/GetReference.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// 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 Xunit;
using System.Runtime.CompilerServices;

using static System.TestHelpers;
using System.Runtime.InteropServices;

namespace System.SpanTests
{
public static partial class MemoryMarshalTests
{
[Fact]
public static void SpanGetReferenceArray()
{
int[] a = { 91, 92, 93, 94, 95 };
Span<int> span = new Span<int>(a, 1, 3);
ref int pinnableReference = ref MemoryMarshal.GetReference(span);
Assert.True(Unsafe.AreSame(ref a[1], ref pinnableReference));
}

[Fact]
public static void SpanGetReferenceArrayPastEnd()
{
// The only real difference between GetReference() and "ref span[0]" is that
// GetReference() of a zero-length won't throw an IndexOutOfRange.

int[] a = { 91, 92, 93, 94, 95 };
Span<int> span = new Span<int>(a, a.Length, 0);
ref int pinnableReference = ref MemoryMarshal.GetReference(span);
ref int expected = ref Unsafe.Add<int>(ref a[a.Length - 1], 1);
Assert.True(Unsafe.AreSame(ref expected, ref pinnableReference));
}

[Fact]
public static void SpanGetReferencePointer()
{
unsafe
{
int i = 42;
Span<int> span = new Span<int>(&i, 1);
ref int pinnableReference = ref MemoryMarshal.GetReference(span);
Assert.True(Unsafe.AreSame(ref i, ref pinnableReference));
}
}

[Fact]
public static void SpanGetReferencePointerDangerousCreate1()
{
TestClass testClass = new TestClass();
Span<char> span = Span<char>.DangerousCreate(testClass, ref testClass.C1, 3);

ref char pinnableReference = ref MemoryMarshal.GetReference(span);
Assert.True(Unsafe.AreSame(ref testClass.C1, ref pinnableReference));
}

[Fact]
public static void SpanGetReferenceEmpty()
{
unsafe
{
Span<int> span = Span<int>.Empty;
ref int pinnableReference = ref MemoryMarshal.GetReference(span);
Assert.True(Unsafe.AreSame(ref Unsafe.AsRef<int>(null), ref pinnableReference));
}
}

[Fact]
public static void ReadOnlySpanGetReferenceArray()
{
int[] a = { 91, 92, 93, 94, 95 };
ReadOnlySpan<int> span = new ReadOnlySpan<int>(a, 1, 3);
ref int pinnableReference = ref Unsafe.AsRef(in MemoryMarshal.GetReference(span));
Assert.True(Unsafe.AreSame(ref a[1], ref pinnableReference));
}

[Fact]
public static void ReadOnlySpanGetReferenceArrayPastEnd()
{
// The only real difference between GetReference() and "ref span[0]" is that
// GetReference() of a zero-length won't throw an IndexOutOfRange.

int[] a = { 91, 92, 93, 94, 95 };
ReadOnlySpan<int> span = new ReadOnlySpan<int>(a, a.Length, 0);
ref int pinnableReference = ref Unsafe.AsRef(in MemoryMarshal.GetReference(span));
ref int expected = ref Unsafe.Add<int>(ref a[a.Length - 1], 1);
Assert.True(Unsafe.AreSame(ref expected, ref pinnableReference));
}

[Fact]
public static void ReadOnlySpanGetReferencePointer()
{
unsafe
{
int i = 42;
ReadOnlySpan<int> span = new ReadOnlySpan<int>(&i, 1);
ref int pinnableReference = ref Unsafe.AsRef(in MemoryMarshal.GetReference(span));
Assert.True(Unsafe.AreSame(ref i, ref pinnableReference));
}
}

[Fact]
public static void ReadOnlySpanGetReferencePointerDangerousCreate1()
{
TestClass testClass = new TestClass();
ReadOnlySpan<char> span = ReadOnlySpan<char>.DangerousCreate(testClass, ref testClass.C1, 3);

ref char pinnableReference = ref Unsafe.AsRef(in MemoryMarshal.GetReference(span));
Assert.True(Unsafe.AreSame(ref testClass.C1, ref pinnableReference));
}

[Fact]
public static void ReadOnlySpanGetReferenceEmpty()
{
unsafe
{
ReadOnlySpan<int> span = ReadOnlySpan<int>.Empty;
ref int pinnableReference = ref Unsafe.AsRef(in MemoryMarshal.GetReference(span));
Assert.True(Unsafe.AreSame(ref Unsafe.AsRef<int>(null), ref pinnableReference));
}
}
}
}
50 changes: 50 additions & 0 deletions src/System.Memory/tests/MemoryMarshal/TryGetArray.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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.Buffers;
using System.Runtime.InteropServices;
using Xunit;

namespace System.MemoryTests
{
public static partial class MemoryMarshalTests
{
[Fact]
public static void ReadOnlyMemoryTryGetArray()
{
int[] array = new int[10];
ReadOnlyMemory<int> memory = array;
Assert.True(MemoryMarshal.TryGetArray(memory, out ArraySegment<int> segment));
Assert.Equal(array.Length, segment.Count);

for (int i = segment.Offset; i < segment.Count + segment.Offset; i++)
{
Assert.Equal(array[i], segment.Array[i]);
}
}

[Fact]
public static void TryGetArrayFromDefaultMemory()
{
ReadOnlyMemory<int> memory = default;
Assert.False(MemoryMarshal.TryGetArray(memory, out ArraySegment<int> segment));
Assert.True(segment.Equals(default));
}

[Fact]
public static void OwnedMemoryTryGetArray()
{
int[] array = new int[10];
OwnedMemory<int> owner = new CustomMemoryForTest<int>(array);
ReadOnlyMemory<int> memory = owner.Memory;
Assert.True(MemoryMarshal.TryGetArray(memory, out ArraySegment<int> segment));
Assert.Equal(array.Length, segment.Count);

for (int i = segment.Offset; i < segment.Count + segment.Offset; i++)
{
Assert.Equal(array[i], segment.Array[i]);
}
}
}
}
Loading