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
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ private sealed class MemoryFileStreamCompletionSource : FileStreamCompletionSour
internal MemoryFileStreamCompletionSource(FileStream stream, int numBufferedBytes, ReadOnlyMemory<byte> memory) :
base(stream, numBufferedBytes, bytes: null) // this type handles the pinning, so null is passed for bytes
{
_handle = memory.Retain(pin: true);
_handle = memory.Pin();
}

internal override void ReleaseNativeResource()
Expand Down
44 changes: 42 additions & 2 deletions src/Common/src/CoreLib/System/Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,49 @@ public Span<T> Span
public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);

/// <summary>
/// Returns a handle for the array.
/// <param name="pin">If pin is true, the GC will not move the array and hence its address can be taken</param>
/// Creates a handle for the memory.
/// The GC will not move the array until the returned <see cref="MemoryHandle"/>
/// is disposed, enabling taking and using the memory's address.
/// </summary>
public unsafe MemoryHandle Pin()
{
if (_index < 0)
{
return ((OwnedMemory<T>)_object).Pin((_index & RemoveOwnedFlagBitMask) * Unsafe.SizeOf<T>());
}
else if (typeof(T) == typeof(char) && _object is string s)
{
// This case can only happen if a ReadOnlyMemory<char> was created around a string
// and then that was cast to a Memory<char> using unsafe / marshaling code. This needs
// to work, however, so that code that uses a single Memory<char> field to store either
// a readable ReadOnlyMemory<char> or a writable Memory<char> can still be pinned and
// used for interop purposes.
GCHandle handle = GCHandle.Alloc(s, GCHandleType.Pinned);
#if FEATURE_PORTABLE_SPAN
void* pointer = Unsafe.Add<T>((void*)handle.AddrOfPinnedObject(), _index);
#else
void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref s.GetRawStringData()), _index);
#endif // FEATURE_PORTABLE_SPAN
return new MemoryHandle(null, pointer, handle);
}
else if (_object is T[] array)
{
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
#if FEATURE_PORTABLE_SPAN
void* pointer = Unsafe.Add<T>((void*)handle.AddrOfPinnedObject(), _index);
#else
void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref array.GetRawSzArrayData()), _index);
#endif // FEATURE_PORTABLE_SPAN
return new MemoryHandle(null, pointer, handle);
}
return default;
}

/// <summary>[Obsolete, use Pin()] Creates a handle for the memory.</summary>
/// <param name="pin">
/// If pin is true, the GC will not move the array until the returned <see cref="MemoryHandle"/>
/// is disposed, enabling taking and using the memory's address.
/// </param>
public unsafe MemoryHandle Retain(bool pin = false)
{
MemoryHandle memoryHandle = default;
Expand Down
38 changes: 36 additions & 2 deletions src/Common/src/CoreLib/System/ReadOnlyMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,44 @@ public ReadOnlySpan<T> Span
/// <param name="destination">The span to copy items into.</param>
public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);

/// <summary>Creates a handle for the memory.</summary>
/// <summary>
/// Creates a handle for the memory.
/// The GC will not move the array until the returned <see cref="MemoryHandle"/>
/// is disposed, enabling taking and using the memory's address.
/// </summary>
public unsafe MemoryHandle Pin()
{
if (_index < 0)
{
return ((OwnedMemory<T>)_object).Pin((_index & RemoveOwnedFlagBitMask) * Unsafe.SizeOf<T>());
}
else if (typeof(T) == typeof(char) && _object is string s)
{
GCHandle handle = GCHandle.Alloc(s, GCHandleType.Pinned);
#if FEATURE_PORTABLE_SPAN
void* pointer = Unsafe.Add<T>((void*)handle.AddrOfPinnedObject(), _index);
#else
void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref s.GetRawStringData()), _index);
#endif // FEATURE_PORTABLE_SPAN
return new MemoryHandle(null, pointer, handle);
}
else if (_object is T[] array)
{
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
#if FEATURE_PORTABLE_SPAN
void* pointer = Unsafe.Add<T>((void*)handle.AddrOfPinnedObject(), _index);
#else
void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref array.GetRawSzArrayData()), _index);
#endif // FEATURE_PORTABLE_SPAN
return new MemoryHandle(null, pointer, handle);
}
return default;
}

/// <summary>[Obsolete, use Pin()] Creates a handle for the memory.</summary>
/// <param name="pin">
/// If pin is true, the GC will not move the array until the returned <see cref="MemoryHandle"/>
/// is disposed, enabling the memory's address can be taken and used.
/// is disposed, enabling taking and using the memory's address.
/// </param>
public unsafe MemoryHandle Retain(bool pin = false)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ internal static int Encrypt(SafeSslHandle context, ReadOnlyMemory<byte> input, r
int retVal;
unsafe
{
using (MemoryHandle handle = input.Retain(pin: true))
using (MemoryHandle handle = input.Pin())
{
retVal = Ssl.SslWrite(context, (byte*)handle.Pointer, input.Length);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ internal unsafe void SetInput(ReadOnlyMemory<byte> inputBuffer)

lock (SyncLock)
{
_inputBufferHandle = inputBuffer.Retain(pin: true);
_inputBufferHandle = inputBuffer.Pin();

_zlibStream.NextIn = (IntPtr)_inputBufferHandle.Pointer;
_zlibStream.AvailIn = (uint)inputBuffer.Length;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ protected PipeCompletionSource(ThreadPoolBoundHandle handle, ReadOnlyMemory<byte
_threadPoolBinding = handle;
_state = NoResult;

_pinnedMemory = bufferToPin.Retain(pin: true);
_pinnedMemory = bufferToPin.Pin();
_overlapped = _threadPoolBinding.AllocateNativeOverlapped((errorCode, numBytes, pOverlapped) =>
{
var completionSource = (PipeCompletionSource<TResult>)ThreadPoolBoundHandle.GetNativeOverlappedState(pOverlapped);
Expand Down
2 changes: 2 additions & 0 deletions src/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public void CopyTo(System.Memory<T> destination) { }
public static implicit operator System.Memory<T> (System.ArraySegment<T> segment) { throw null; }
public static implicit operator System.ReadOnlyMemory<T> (System.Memory<T> memory) { throw null; }
public static implicit operator System.Memory<T> (T[] array) { throw null; }
public System.Buffers.MemoryHandle Pin() { throw null; }
public System.Buffers.MemoryHandle Retain(bool pin = false) { throw null; }
public System.Memory<T> Slice(int start) { throw null; }
public System.Memory<T> Slice(int start, int length) { throw null; }
Expand All @@ -131,6 +132,7 @@ public void CopyTo(System.Memory<T> destination) { }
public override int GetHashCode() { throw null; }
public static implicit operator System.ReadOnlyMemory<T> (System.ArraySegment<T> segment) { throw null; }
public static implicit operator System.ReadOnlyMemory<T> (T[] array) { throw null; }
public System.Buffers.MemoryHandle Pin() { throw null; }
public System.Buffers.MemoryHandle Retain(bool pin = false) { throw null; }
public System.ReadOnlyMemory<T> Slice(int start) { throw null; }
public System.ReadOnlyMemory<T> Slice(int start, int length) { throw null; }
Expand Down
133 changes: 133 additions & 0 deletions src/System.Memory/tests/Memory/Pin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// 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 Xunit;

namespace System.MemoryTests
{
public static partial class MemoryTests
{
[Fact]
public static void MemoryPin()
{
int[] array = { 1, 2, 3, 4, 5 };
Memory<int> memory = array;
MemoryHandle handle = memory.Pin();
Assert.True(handle.HasPointer);
unsafe
{
int* pointer = (int*)handle.Pointer;

GC.Collect();

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i], pointer[i]);
}
}
handle.Dispose();
}

[Fact]
public static void MemoryFromEmptyArrayPin()
{
Memory<int> memory = new int[0];
MemoryHandle handle = memory.Pin();
Assert.True(handle.HasPointer);
handle.Dispose();
}

[Fact]
public static void DefaultMemoryPin()
{
Memory<int> memory = default;
MemoryHandle handle = memory.Pin();
Assert.False(handle.HasPointer);
unsafe
{
Assert.True(handle.Pointer == null);
}
handle.Dispose();
}

[Fact]
public static void MemoryPinAndSlice()
{
int[] array = { 1, 2, 3, 4, 5 };
Memory<int> memory = array;
memory = memory.Slice(1);
MemoryHandle handle = memory.Pin();
Span<int> span = memory.Span;
Assert.True(handle.HasPointer);
unsafe
{
int* pointer = (int*)handle.Pointer;

GC.Collect();

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i + 1], pointer[i]);
}

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i + 1], span[i]);
}
}
handle.Dispose();
}

[Fact]
public static void OwnedMemoryPin()
{
int[] array = { 1, 2, 3, 4, 5 };
OwnedMemory<int> owner = new CustomMemoryForTest<int>(array);
Memory<int> memory = owner.Memory;
MemoryHandle handle = memory.Pin();
Assert.True(handle.HasPointer);
unsafe
{
int* pointer = (int*)handle.Pointer;

GC.Collect();

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i], pointer[i]);
}
}
handle.Dispose();
}

[Fact]
public static void OwnedMemoryPinAndSlice()
{
int[] array = { 1, 2, 3, 4, 5 };
OwnedMemory<int> owner = new CustomMemoryForTest<int>(array);
Memory<int> memory = owner.Memory.Slice(1);
MemoryHandle handle = memory.Pin();
Span<int> span = memory.Span;
Assert.True(handle.HasPointer);
unsafe
{
int* pointer = (int*)handle.Pointer;

GC.Collect();

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i + 1], pointer[i]);
}

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i + 1], span[i]);
}
}
handle.Dispose();
}
}
}
48 changes: 24 additions & 24 deletions src/System.Memory/tests/Memory/Retain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,30 @@ public static void MemoryRetainWithPinning()
handle.Dispose();
}

[Fact]
public static void MemoryFromEmptyArrayRetainWithPinning()
{
Memory<int> memory = new int[0];
MemoryHandle handle = memory.Retain(pin: true);
Assert.True(handle.HasPointer);
handle.Dispose();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public static void DefaultMemoryRetain(bool pin)
{
Memory<int> memory = default;
MemoryHandle handle = memory.Retain(pin: pin);
Assert.False(handle.HasPointer);
unsafe
{
Assert.True(handle.Pointer == null);
}
handle.Dispose();
}

[Fact]
public static void MemoryRetainWithPinningAndSlice()
{
Expand Down Expand Up @@ -110,15 +134,6 @@ public static void OwnedMemoryRetainWithPinning()
handle.Dispose();
}

[Fact]
public static void MemoryFromEmptyArrayRetainWithPinning()
{
Memory<int> memory = new int[0];
MemoryHandle handle = memory.Retain(pin: true);
Assert.True(handle.HasPointer);
handle.Dispose();
}

[Fact]
public static void OwnedMemoryRetainWithPinningAndSlice()
{
Expand Down Expand Up @@ -146,20 +161,5 @@ public static void OwnedMemoryRetainWithPinningAndSlice()
}
handle.Dispose();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public static void DefaultMemoryRetain(bool pin)
{
Memory<int> memory = default;
MemoryHandle handle = memory.Retain(pin: pin);
Assert.False(handle.HasPointer);
unsafe
{
Assert.True(handle.Pointer == null);
}
handle.Dispose();
}
}
}
9 changes: 2 additions & 7 deletions src/System.Memory/tests/Memory/Strings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,13 @@ public static void Memory_Slice_MatchesSubstring(string input, int offset, int c
}

[Fact]
public static unsafe void Memory_Retain_ExpectedPointerValue()
public static unsafe void Memory_Pin_ExpectedPointerValue()
{
string input = "0123456789";
ReadOnlyMemory<char> readonlyMemory = input.AsMemory();
Memory<char> m = MemoryMarshal.AsMemory(readonlyMemory);

using (MemoryHandle h = m.Retain(pin: false))
{
Assert.Equal(IntPtr.Zero, (IntPtr)h.Pointer);
}

using (MemoryHandle h = m.Retain(pin: true))
using (MemoryHandle h = m.Pin())
{
GC.Collect();
fixed (char* ptr = input)
Expand Down
Loading