diff options
Diffstat (limited to 'src/System.Private.CoreLib/shared/System/Memory.cs')
-rw-r--r-- | src/System.Private.CoreLib/shared/System/Memory.cs | 186 |
1 files changed, 119 insertions, 67 deletions
diff --git a/src/System.Private.CoreLib/shared/System/Memory.cs b/src/System.Private.CoreLib/shared/System/Memory.cs index fca015f5e..1a7556720 100644 --- a/src/System.Private.CoreLib/shared/System/Memory.cs +++ b/src/System.Private.CoreLib/shared/System/Memory.cs @@ -8,9 +8,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute; using EditorBrowsableState = System.ComponentModel.EditorBrowsableState; -#if !FEATURE_PORTABLE_SPAN + using Internal.Runtime.CompilerServices; -#endif // FEATURE_PORTABLE_SPAN namespace System { @@ -18,23 +17,26 @@ namespace System /// Memory represents a contiguous region of arbitrary memory similar to <see cref="Span{T}"/>. /// Unlike <see cref="Span{T}"/>, it is not a byref-like type. /// </summary> - [DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerTypeProxy(typeof(MemoryDebugView<>))] + [DebuggerDisplay("{ToString(),raw}")] public readonly struct Memory<T> { // NOTE: With the current implementation, Memory<T> and ReadOnlyMemory<T> must have the same layout, // as code uses Unsafe.As to cast between them. // The highest order bit of _index is used to discern whether _object is an array/string or an owned memory - // if (_index >> 31) == 1, object _object is an OwnedMemory<T> - // else, object _object is a T[] or a string. It can only be a string if the Memory<T> was created by + // if (_index >> 31) == 1, object _object is an MemoryManager<T> + // else, object _object is a T[] or a string. + // if (_length >> 31) == 1, _object is a pre-pinned array, so Pin() will not allocate a new GCHandle + // else, Pin() needs to allocate a new GCHandle to pin the object. + // It can only be a string if the Memory<T> was created by // using unsafe / marshaling code to reinterpret a ReadOnlyMemory<char> wrapped around a string as // a Memory<T>. private readonly object _object; private readonly int _index; private readonly int _length; - private const int RemoveOwnedFlagBitMask = 0x7FFFFFFF; + private const int RemoveFlagsBitMask = 0x7FFFFFFF; /// <summary> /// Creates a new memory over the entirety of the target array. @@ -110,22 +112,61 @@ namespace System _length = length; } - // Constructor for internal use only. + /// <summary> + /// Creates a new memory from a memory manager that provides specific method implementations beginning + /// at 0 index and ending at 'end' index (exclusive). + /// </summary> + /// <param name="manager">The memory manager.</param> + /// <param name="length">The number of items in the memory.</param> + /// <exception cref="System.ArgumentOutOfRangeException"> + /// Thrown when the specified <paramref name="length"/> is negative. + /// </exception> + /// <remarks>For internal infrastructure only</remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Memory(OwnedMemory<T> owner, int index, int length) + internal Memory(MemoryManager<T> manager, int length) { - // No validation performed; caller must provide any necessary validation. - _object = owner; - _index = index | (1 << 31); // Before using _index, check if _index < 0, then 'and' it with RemoveOwnedFlagBitMask + Debug.Assert(manager != null); + + if (length < 0) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + _object = manager; + _index = (1 << 31); // Mark as MemoryManager type + // Before using _index, check if _index < 0, then 'and' it with RemoveFlagsBitMask + _length = length; + } + + /// <summary> + /// Creates a new memory from a memory manager that provides specific method implementations beginning + /// at 'start' index and ending at 'end' index (exclusive). + /// </summary> + /// <param name="manager">The memory manager.</param> + /// <param name="start">The index at which to begin the memory.</param> + /// <param name="length">The number of items in the memory.</param> + /// <exception cref="System.ArgumentOutOfRangeException"> + /// Thrown when the specified <paramref name="start"/> or <paramref name="length"/> is negative. + /// </exception> + /// <remarks>For internal infrastructure only</remarks> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Memory(MemoryManager<T> manager, int start, int length) + { + Debug.Assert(manager != null); + + if (length < 0 || start < 0) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + _object = manager; + _index = start | (1 << 31); // Mark as MemoryManager type + // Before using _index, check if _index < 0, then 'and' it with RemoveFlagsBitMask _length = length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Memory(object obj, int index, int length) + internal Memory(object obj, int start, int length) { // No validation performed; caller must provide any necessary validation. _object = obj; - _index = index; + _index = start; _length = length; } @@ -137,7 +178,7 @@ namespace System /// <summary> /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="Memory{T}"/> /// </summary> - public static implicit operator Memory<T>(ArraySegment<T> arraySegment) => new Memory<T>(arraySegment.Array, arraySegment.Offset, arraySegment.Count); + public static implicit operator Memory<T>(ArraySegment<T> segment) => new Memory<T>(segment.Array, segment.Offset, segment.Count); /// <summary> /// Defines an implicit conversion of a <see cref="Memory{T}"/> to a <see cref="ReadOnlyMemory{T}"/> @@ -145,9 +186,6 @@ namespace System public static implicit operator ReadOnlyMemory<T>(Memory<T> memory) => Unsafe.As<Memory<T>, ReadOnlyMemory<T>>(ref memory); - //Debugger Display = {T[length]} - private string DebuggerDisplay => string.Format("{{{0}[{1}]}}", typeof(T).Name, _length); - /// <summary> /// Returns an empty <see cref="Memory{T}"/> /// </summary> @@ -156,12 +194,25 @@ namespace System /// <summary> /// The number of items in the memory. /// </summary> - public int Length => _length; + public int Length => _length & RemoveFlagsBitMask; /// <summary> /// Returns true if Length is 0. /// </summary> - public bool IsEmpty => _length == 0; + public bool IsEmpty => (_length & RemoveFlagsBitMask) == 0; + + /// <summary> + /// For <see cref="Memory{Char}"/>, returns a new instance of string that represents the characters pointed to by the memory. + /// Otherwise, returns a <see cref="string"/> with the name of the type and the number of elements. + /// </summary> + public override string ToString() + { + if (typeof(T) == typeof(char)) + { + return (_object is string str) ? str.Substring(_index, _length & RemoveFlagsBitMask) : Span.ToString(); + } + return string.Format("System.Memory<{0}>[{1}]", typeof(T).Name, _length & RemoveFlagsBitMask); + } /// <summary> /// Forms a slice out of the given memory, beginning at 'start'. @@ -173,12 +224,16 @@ namespace System [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory<T> Slice(int start) { - if ((uint)start > (uint)_length) + // Used to maintain the high-bit which indicates whether the Memory has been pre-pinned or not. + int capturedLength = _length; + int actualLength = capturedLength & RemoveFlagsBitMask; + if ((uint)start > (uint)actualLength) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); } - return new Memory<T>(_object, _index + start, _length - start); + // It is expected for (capturedLength - start) to be negative if the memory is already pre-pinned. + return new Memory<T>(_object, _index + start, capturedLength - start); } /// <summary> @@ -192,12 +247,16 @@ namespace System [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory<T> Slice(int start, int length) { - if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) + // Used to maintain the high-bit which indicates whether the Memory has been pre-pinned or not. + int capturedLength = _length; + int actualLength = capturedLength & RemoveFlagsBitMask; + if ((uint)start > (uint)actualLength || (uint)length > (uint)(actualLength - start)) { ThrowHelper.ThrowArgumentOutOfRangeException(); } - return new Memory<T>(_object, _index + start, length); + // Set the high-bit to match the this._length high bit (1 for pre-pinned, 0 for unpinned). + return new Memory<T>(_object, _index + start, length | (capturedLength & ~RemoveFlagsBitMask)); } /// <summary> @@ -210,24 +269,23 @@ namespace System { if (_index < 0) { - return ((OwnedMemory<T>)_object).Span.Slice(_index & RemoveOwnedFlagBitMask, _length); + Debug.Assert(_length >= 0); + Debug.Assert(_object != null); + return ((MemoryManager<T>)_object).GetSpan().Slice(_index & RemoveFlagsBitMask, _length); } else if (typeof(T) == typeof(char) && _object is string s) { + Debug.Assert(_length >= 0); // This is dangerous, returning a writable span for a string that should be immutable. // However, we need to handle the case where a ReadOnlyMemory<char> was created from a string // and then cast to a Memory<T>. Such a cast can only be done with unsafe or marshaling code, // in which case that's the dangerous operation performed by the dev, and we're just following // suit here to make it work as best as possible. -#if FEATURE_PORTABLE_SPAN - return new Span<T>(Unsafe.As<Pinnable<T>>(s), MemoryExtensions.StringAdjustment, s.Length).Slice(_index, _length); -#else return new Span<T>(ref Unsafe.As<char, T>(ref s.GetRawStringData()), s.Length).Slice(_index, _length); -#endif // FEATURE_PORTABLE_SPAN } else if (_object != null) { - return new Span<T>((T[])_object, _index, _length); + return new Span<T>((T[])_object, _index, _length & RemoveFlagsBitMask); } else { @@ -260,53 +318,47 @@ namespace System 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 memory until the returned <see cref="MemoryHandle"/> + /// is disposed, enabling taking and using the memory's address. + /// <exception cref="System.ArgumentException"> + /// An instance with nonprimitive (non-blittable) members cannot be pinned. + /// </exception> /// </summary> - public unsafe MemoryHandle Retain(bool pin = false) + public unsafe MemoryHandle Pin() { - MemoryHandle memoryHandle = default; - if (pin) + if (_index < 0) { - if (_index < 0) - { - memoryHandle = ((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 - memoryHandle = new MemoryHandle(null, pointer, handle); - } - else if (_object is T[] array) + Debug.Assert(_object != null); + return ((MemoryManager<T>)_object).Pin((_index & RemoveFlagsBitMask)); + } + 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); + void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref s.GetRawStringData()), _index); + return new MemoryHandle(pointer, handle); + } + else if (_object is T[] array) + { + // Array is already pre-pinned + if (_length < 0) { - 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 - memoryHandle = new MemoryHandle(null, pointer, handle); + return new MemoryHandle(pointer); } - } - else - { - if (_index < 0) + else { - ((OwnedMemory<T>)_object).Retain(); - memoryHandle = new MemoryHandle((OwnedMemory<T>)_object); + GCHandle handle = GCHandle.Alloc(array, GCHandleType.Pinned); + void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref array.GetRawSzArrayData()), _index); + return new MemoryHandle(pointer, handle); } } - return memoryHandle; + return default; } /// <summary> |