// 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.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute; using EditorBrowsableState = System.ComponentModel.EditorBrowsableState; using Internal.Runtime.CompilerServices; namespace System { /// /// Memory represents a contiguous region of arbitrary memory similar to . /// Unlike , it is not a byref-like type. /// [DebuggerTypeProxy(typeof(MemoryDebugView<>))] [DebuggerDisplay("{ToString(),raw}")] public readonly struct Memory { // NOTE: With the current implementation, Memory and ReadOnlyMemory 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 a pre-pinned array. // (_index < 0) => _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; /// /// Creates a new memory over the entirety of the target array. /// /// The target array. /// Returns default when is null. /// Thrown when is covariant and array's type is not exactly T[]. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory(T[] array) { if (array == null) { this = default; return; // returns default } if (default(T) == null && array.GetType() != typeof(T[])) ThrowHelper.ThrowArrayTypeMismatchException(); _object = array; _index = 0; _length = array.Length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Memory(T[] array, int start) { if (array == null) { if (start != 0) ThrowHelper.ThrowArgumentOutOfRangeException(); this = default; return; // returns default } if (default(T) == null && array.GetType() != typeof(T[])) ThrowHelper.ThrowArrayTypeMismatchException(); if ((uint)start > (uint)array.Length) ThrowHelper.ThrowArgumentOutOfRangeException(); _object = array; _index = start; _length = array.Length - start; } /// /// Creates a new memory over the portion of the target array beginning /// at 'start' index and ending at 'end' index (exclusive). /// /// The target array. /// The index at which to begin the memory. /// The number of items in the memory. /// Returns default when is null. /// Thrown when is covariant and array's type is not exactly T[]. /// /// Thrown when the specified or end index is not in the range (<0 or >=Length). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory(T[] array, int start, int length) { if (array == null) { if (start != 0 || length != 0) ThrowHelper.ThrowArgumentOutOfRangeException(); this = default; return; // returns default } if (default(T) == null && array.GetType() != typeof(T[])) ThrowHelper.ThrowArrayTypeMismatchException(); #if BIT64 // See comment in Span.Slice for how this works. if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)array.Length) ThrowHelper.ThrowArgumentOutOfRangeException(); #else if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) ThrowHelper.ThrowArgumentOutOfRangeException(); #endif _object = array; _index = start; _length = length; } /// /// Creates a new memory from a memory manager that provides specific method implementations beginning /// at 0 index and ending at 'end' index (exclusive). /// /// The memory manager. /// The number of items in the memory. /// /// Thrown when the specified is negative. /// /// For internal infrastructure only [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Memory(MemoryManager manager, int length) { Debug.Assert(manager != null); if (length < 0) ThrowHelper.ThrowArgumentOutOfRangeException(); _object = manager; _index = 0; _length = length; } /// /// Creates a new memory from a memory manager that provides specific method implementations beginning /// at 'start' index and ending at 'end' index (exclusive). /// /// The memory manager. /// The index at which to begin the memory. /// The number of items in the memory. /// /// Thrown when the specified or is negative. /// /// For internal infrastructure only [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Memory(MemoryManager manager, int start, int length) { Debug.Assert(manager != null); if (length < 0 || start < 0) ThrowHelper.ThrowArgumentOutOfRangeException(); _object = manager; _index = start; _length = length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Memory(object obj, int start, int length) { // No validation performed in release builds; caller must provide any necessary validation. // 'obj is T[]' below also handles things like int[] <-> uint[] being convertible Debug.Assert((obj == null) || (typeof(T) == typeof(char) && obj is string) || (obj is T[]) || (obj is MemoryManager)); _object = obj; _index = start; _length = length; } /// /// Defines an implicit conversion of an array to a /// public static implicit operator Memory(T[] array) => new Memory(array); /// /// Defines an implicit conversion of a to a /// public static implicit operator Memory(ArraySegment segment) => new Memory(segment.Array, segment.Offset, segment.Count); /// /// Defines an implicit conversion of a to a /// public static implicit operator ReadOnlyMemory(Memory memory) => Unsafe.As, ReadOnlyMemory>(ref memory); /// /// Returns an empty /// public static Memory Empty => default; /// /// The number of items in the memory. /// public int Length => _length; /// /// Returns true if Length is 0. /// public bool IsEmpty => _length == 0; /// /// For , returns a new instance of string that represents the characters pointed to by the memory. /// Otherwise, returns a with the name of the type and the number of elements. /// public override string ToString() { if (typeof(T) == typeof(char)) { return (_object is string str) ? str.Substring(_index, _length) : Span.ToString(); } return string.Format("System.Memory<{0}>[{1}]", typeof(T).Name, _length); } /// /// Forms a slice out of the given memory, beginning at 'start'. /// /// The index at which to begin this slice. /// /// Thrown when the specified index is not in range (<0 or >=Length). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory Slice(int start) { if ((uint)start > (uint)_length) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); } // It is expected for _index + start to be negative if the memory is already pre-pinned. return new Memory(_object, _index + start, _length - start); } /// /// Forms a slice out of the given memory, beginning at 'start', of given length /// /// The index at which to begin this slice. /// The desired length for the slice (exclusive). /// /// Thrown when the specified or end index is not in range (<0 or >=Length). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory Slice(int start, int length) { #if BIT64 // See comment in Span.Slice for how this works. if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)_length) ThrowHelper.ThrowArgumentOutOfRangeException(); #else if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) ThrowHelper.ThrowArgumentOutOfRangeException(); #endif // It is expected for _index + start to be negative if the memory is already pre-pinned. return new Memory(_object, _index + start, length); } /// /// Forms a slice out of the given memory, beginning at 'startIndex' /// /// The index at which to begin this slice. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory Slice(Index startIndex) { int actualIndex = startIndex.GetOffset(_length); return Slice(actualIndex); } /// /// Forms a slice out of the given memory using the range start and end indexes. /// /// The range used to slice the memory using its start and end indexes. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory Slice(Range range) { (int start, int length) = range.GetOffsetAndLength(_length); // It is expected for _index + start to be negative if the memory is already pre-pinned. return new Memory(_object, _index + start, length); } /// /// Forms a slice out of the given memory using the range start and end indexes. /// /// The range used to slice the memory using its start and end indexes. public Memory this[Range range] => Slice(range); /// /// Returns a span from the memory. /// public unsafe Span Span { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { // This property getter has special support for returning a mutable Span that wraps // an immutable String instance. This is obviously a dangerous feature and breaks type safety. // However, we need to handle the case where a ReadOnlyMemory was created from a string // and then cast to a Memory. 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. ref T refToReturn = ref Unsafe.AsRef(null); int lengthOfUnderlyingSpan = 0; // Copy this field into a local so that it can't change out from under us mid-operation. object tmpObject = _object; if (tmpObject != null) { if (typeof(T) == typeof(char) && tmpObject.GetType() == typeof(string)) { // Special-case string since it's the most common for ROM. refToReturn = ref Unsafe.As(ref Unsafe.As(tmpObject).GetRawStringData()); lengthOfUnderlyingSpan = Unsafe.As(tmpObject).Length; } else if (RuntimeHelpers.ObjectHasComponentSize(tmpObject)) { // We know the object is not null, it's not a string, and it is variable-length. The only // remaining option is for it to be a T[] (or a U[] which is blittable to T[], like int[] // and uint[]). Otherwise somebody used private reflection to set this field, and we're not // too worried about type safety violations at this point. // 'tmpObject is T[]' below also handles things like int[] <-> uint[] being convertible Debug.Assert(tmpObject is T[]); refToReturn = ref Unsafe.As(ref Unsafe.As(tmpObject).GetRawSzArrayData()); lengthOfUnderlyingSpan = Unsafe.As(tmpObject).Length; } else { // We know the object is not null, and it's not variable-length, so it must be a MemoryManager. // Otherwise somebody used private reflection to set this field, and we're not too worried about // type safety violations at that point. Note that it can't be a MemoryManager, even if U and // T are blittable (e.g., MemoryManager to MemoryManager), since there exists no // constructor or other public API which would allow such a conversion. Debug.Assert(tmpObject is MemoryManager); Span memoryManagerSpan = Unsafe.As>(tmpObject).GetSpan(); refToReturn = ref MemoryMarshal.GetReference(memoryManagerSpan); lengthOfUnderlyingSpan = memoryManagerSpan.Length; } // If the Memory or ReadOnlyMemory instance is torn, this property getter has undefined behavior. // We try to detect this condition and throw an exception, but it's possible that a torn struct might // appear to us to be valid, and we'll return an undesired span. Such a span is always guaranteed at // least to be in-bounds when compared with the original Memory instance, so using the span won't // AV the process. int desiredStartIndex = _index & ReadOnlyMemory.RemoveFlagsBitMask; int desiredLength = _length; #if BIT64 // See comment in Span.Slice for how this works. if ((ulong)(uint)desiredStartIndex + (ulong)(uint)desiredLength > (ulong)(uint)lengthOfUnderlyingSpan) { ThrowHelper.ThrowArgumentOutOfRangeException(); } #else if ((uint)desiredStartIndex > (uint)lengthOfUnderlyingSpan || (uint)desiredLength > (uint)(lengthOfUnderlyingSpan - desiredStartIndex)) { ThrowHelper.ThrowArgumentOutOfRangeException(); } #endif refToReturn = ref Unsafe.Add(ref refToReturn, desiredStartIndex); lengthOfUnderlyingSpan = desiredLength; } return new Span(ref refToReturn, lengthOfUnderlyingSpan); } } /// /// Copies the contents of the memory into the destination. If the source /// and destination overlap, this method behaves as if the original values are in /// a temporary location before the destination is overwritten. /// /// The Memory to copy items into. /// /// Thrown when the destination is shorter than the source. /// /// public void CopyTo(Memory destination) => Span.CopyTo(destination.Span); /// /// Copies the contents of the memory into the destination. If the source /// and destination overlap, this method behaves as if the original values are in /// a temporary location before the destination is overwritten. /// /// If the destination is shorter than the source, this method /// return false and no data is written to the destination. /// /// The span to copy items into. public bool TryCopyTo(Memory destination) => Span.TryCopyTo(destination.Span); /// /// Creates a handle for the memory. /// The GC will not move the memory until the returned /// is disposed, enabling taking and using the memory's address. /// /// An instance with nonprimitive (non-blittable) members cannot be pinned. /// /// public unsafe MemoryHandle Pin() { // Just like the Span property getter, we have special support for a mutable Memory // that wraps an immutable String instance. This might happen if a caller creates an // immutable ROM wrapping a String, then uses Unsafe.As to create a mutable M. // This needs to work, however, so that code that uses a single Memory field to store either // a readable ReadOnlyMemory or a writable Memory can still be pinned and // used for interop purposes. // It's possible that the below logic could result in an AV if the struct // is torn. This is ok since the caller is expecting to use raw pointers, // and we're not required to keep this as safe as the other Span-based APIs. object tmpObject = _object; if (tmpObject != null) { if (typeof(T) == typeof(char) && tmpObject is string s) { GCHandle handle = GCHandle.Alloc(tmpObject, GCHandleType.Pinned); ref char stringData = ref Unsafe.Add(ref s.GetRawStringData(), _index); return new MemoryHandle(Unsafe.AsPointer(ref stringData), handle); } else if (RuntimeHelpers.ObjectHasComponentSize(tmpObject)) { // 'tmpObject is T[]' below also handles things like int[] <-> uint[] being convertible Debug.Assert(tmpObject is T[]); // Array is already pre-pinned if (_index < 0) { void* pointer = Unsafe.Add(Unsafe.AsPointer(ref Unsafe.As(tmpObject).GetRawSzArrayData()), _index & ReadOnlyMemory.RemoveFlagsBitMask); return new MemoryHandle(pointer); } else { GCHandle handle = GCHandle.Alloc(tmpObject, GCHandleType.Pinned); void* pointer = Unsafe.Add(Unsafe.AsPointer(ref Unsafe.As(tmpObject).GetRawSzArrayData()), _index); return new MemoryHandle(pointer, handle); } } else { Debug.Assert(tmpObject is MemoryManager); return Unsafe.As>(tmpObject).Pin(_index); } } return default; } /// /// Copies the contents from the memory into a new array. This heap /// allocates, so should generally be avoided, however it is sometimes /// necessary to bridge the gap with APIs written in terms of arrays. /// public T[] ToArray() => Span.ToArray(); /// /// Determines whether the specified object is equal to the current object. /// Returns true if the object is Memory or ReadOnlyMemory and if both objects point to the same array and have the same length. /// [EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object obj) { if (obj is ReadOnlyMemory) { return ((ReadOnlyMemory)obj).Equals(this); } else if (obj is Memory memory) { return Equals(memory); } else { return false; } } /// /// Returns true if the memory points to the same array and has the same length. Note that /// this does *not* check to see if the *contents* are equal. /// public bool Equals(Memory other) { return _object == other._object && _index == other._index && _length == other._length; } /// /// Serves as the default hash function. /// [EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() { // We use RuntimeHelpers.GetHashCode instead of Object.GetHashCode because the hash // code is based on object identity and referential equality, not deep equality (as common with string). return (_object != null) ? HashCode.Combine(RuntimeHelpers.GetHashCode(_object), _index, _length) : 0; } } }