diff options
Diffstat (limited to 'src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs')
-rw-r--r-- | src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs | 127 |
1 files changed, 69 insertions, 58 deletions
diff --git a/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs b/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs index 166a204ed..02445ec5c 100644 --- a/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs +++ b/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.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,21 +17,23 @@ namespace System /// Represents a contiguous region of memory, similar to <see cref="ReadOnlySpan{T}"/>. /// Unlike <see cref="ReadOnlySpan{T}"/>, it is not a byref-like type. /// </summary> - [DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerTypeProxy(typeof(MemoryDebugView<>))] + [DebuggerDisplay("{ToString(),raw}")] public readonly struct ReadOnlyMemory<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 is an OwnedMemory<T> - // else, _object is a T[] or string + // if (_index >> 31) == 1, _object is an MemoryManager<T> + // else, _object is a T[] or 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. private readonly object _object; private readonly int _index; private readonly int _length; - internal const int RemoveOwnedFlagBitMask = 0x7FFFFFFF; + internal const int RemoveFlagsBitMask = 0x7FFFFFFF; /// <summary> /// Creates a new memory over the entirety of the target array. @@ -97,9 +98,6 @@ namespace System _length = length; } - //Debugger Display = {T[length]} - private string DebuggerDisplay => string.Format("{{{0}[{1}]}}", typeof(T).Name, _length); - /// <summary> /// Defines an implicit conversion of an array to a <see cref="ReadOnlyMemory{T}"/> /// </summary> @@ -108,7 +106,7 @@ namespace System /// <summary> /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="ReadOnlyMemory{T}"/> /// </summary> - public static implicit operator ReadOnlyMemory<T>(ArraySegment<T> arraySegment) => new ReadOnlyMemory<T>(arraySegment.Array, arraySegment.Offset, arraySegment.Count); + public static implicit operator ReadOnlyMemory<T>(ArraySegment<T> segment) => new ReadOnlyMemory<T>(segment.Array, segment.Offset, segment.Count); /// <summary> /// Returns an empty <see cref="ReadOnlyMemory{T}"/> @@ -118,12 +116,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="ReadOnlyMemory{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.ReadOnlyMemory<{0}>[{1}]", typeof(T).Name, _length & RemoveFlagsBitMask); + } /// <summary> /// Forms a slice out of the given memory, beginning at 'start'. @@ -135,12 +146,16 @@ namespace System [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlyMemory<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 ReadOnlyMemory<T>(_object, _index + start, _length - start); + // It is expected for (capturedLength - start) to be negative if the memory is already pre-pinned. + return new ReadOnlyMemory<T>(_object, _index + start, capturedLength - start); } /// <summary> @@ -154,12 +169,16 @@ namespace System [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlyMemory<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 = _length & RemoveFlagsBitMask; + if ((uint)start > (uint)actualLength || (uint)length > (uint)(actualLength - start)) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); } - return new ReadOnlyMemory<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 ReadOnlyMemory<T>(_object, _index + start, length | (capturedLength & ~RemoveFlagsBitMask)); } /// <summary> @@ -172,19 +191,18 @@ 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) { -#if FEATURE_PORTABLE_SPAN - return new ReadOnlySpan<T>(Unsafe.As<Pinnable<T>>(s), MemoryExtensions.StringAdjustment, s.Length).Slice(_index, _length); -#else + Debug.Assert(_length >= 0); return new ReadOnlySpan<T>(ref Unsafe.As<char, T>(ref s.GetRawStringData()), s.Length).Slice(_index, _length); -#endif // FEATURE_PORTABLE_SPAN } else if (_object != null) { - return new ReadOnlySpan<T>((T[])_object, _index, _length); + return new ReadOnlySpan<T>((T[])_object, _index, _length & RemoveFlagsBitMask); } else { @@ -216,50 +234,43 @@ namespace System /// <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> - /// <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. - /// </param> - public unsafe MemoryHandle Retain(bool pin = false) + /// <summary> + /// 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 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) - { - 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) + { + 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> |