Fix pragma warning restore (dotnet/coreclr#26389)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Memory.cs
blob25f5d8f7173bb91e0ef289c1ba93926d85af12ed
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.Buffers;
6 using System.Diagnostics;
7 using System.Runtime.CompilerServices;
8 using System.Runtime.InteropServices;
9 using System.Text;
10 using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute;
11 using EditorBrowsableState = System.ComponentModel.EditorBrowsableState;
13 using Internal.Runtime.CompilerServices;
15 #pragma warning disable SA1121 // explicitly using type aliases instead of built-in types
16 #if BIT64
17 using nuint = System.UInt64;
18 #else // BIT64
19 using nuint = System.UInt32;
20 #endif // BIT64
22 namespace System
24 /// <summary>
25 /// Memory represents a contiguous region of arbitrary memory similar to <see cref="Span{T}"/>.
26 /// Unlike <see cref="Span{T}"/>, it is not a byref-like type.
27 /// </summary>
28 [DebuggerTypeProxy(typeof(MemoryDebugView<>))]
29 [DebuggerDisplay("{ToString(),raw}")]
30 public readonly struct Memory<T> : IEquatable<Memory<T>>
32 // NOTE: With the current implementation, Memory<T> and ReadOnlyMemory<T> must have the same layout,
33 // as code uses Unsafe.As to cast between them.
35 // The highest order bit of _index is used to discern whether _object is a pre-pinned array.
36 // (_index < 0) => _object is a pre-pinned array, so Pin() will not allocate a new GCHandle
37 // (else) => Pin() needs to allocate a new GCHandle to pin the object.
38 private readonly object? _object;
39 private readonly int _index;
40 private readonly int _length;
42 /// <summary>
43 /// Creates a new memory over the entirety of the target array.
44 /// </summary>
45 /// <param name="array">The target array.</param>
46 /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
47 /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
48 [MethodImpl(MethodImplOptions.AggressiveInlining)]
49 public Memory(T[]? array)
51 if (array == null)
53 this = default;
54 return; // returns default
56 if (default(T)! == null && array.GetType() != typeof(T[])) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
57 ThrowHelper.ThrowArrayTypeMismatchException();
59 _object = array;
60 _index = 0;
61 _length = array.Length;
64 [MethodImpl(MethodImplOptions.AggressiveInlining)]
65 internal Memory(T[]? array, int start)
67 if (array == null)
69 if (start != 0)
70 ThrowHelper.ThrowArgumentOutOfRangeException();
71 this = default;
72 return; // returns default
74 if (default(T)! == null && array.GetType() != typeof(T[])) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
75 ThrowHelper.ThrowArrayTypeMismatchException();
76 if ((uint)start > (uint)array.Length)
77 ThrowHelper.ThrowArgumentOutOfRangeException();
79 _object = array;
80 _index = start;
81 _length = array.Length - start;
84 /// <summary>
85 /// Creates a new memory over the portion of the target array beginning
86 /// at 'start' index and ending at 'end' index (exclusive).
87 /// </summary>
88 /// <param name="array">The target array.</param>
89 /// <param name="start">The index at which to begin the memory.</param>
90 /// <param name="length">The number of items in the memory.</param>
91 /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
92 /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
93 /// <exception cref="System.ArgumentOutOfRangeException">
94 /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;Length).
95 /// </exception>
96 [MethodImpl(MethodImplOptions.AggressiveInlining)]
97 public Memory(T[]? array, int start, int length)
99 if (array == null)
101 if (start != 0 || length != 0)
102 ThrowHelper.ThrowArgumentOutOfRangeException();
103 this = default;
104 return; // returns default
106 if (default(T)! == null && array.GetType() != typeof(T[])) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
107 ThrowHelper.ThrowArrayTypeMismatchException();
108 #if BIT64
109 // See comment in Span<T>.Slice for how this works.
110 if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)array.Length)
111 ThrowHelper.ThrowArgumentOutOfRangeException();
112 #else
113 if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
114 ThrowHelper.ThrowArgumentOutOfRangeException();
115 #endif
117 _object = array;
118 _index = start;
119 _length = length;
122 /// <summary>
123 /// Creates a new memory from a memory manager that provides specific method implementations beginning
124 /// at 0 index and ending at 'end' index (exclusive).
125 /// </summary>
126 /// <param name="manager">The memory manager.</param>
127 /// <param name="length">The number of items in the memory.</param>
128 /// <exception cref="System.ArgumentOutOfRangeException">
129 /// Thrown when the specified <paramref name="length"/> is negative.
130 /// </exception>
131 /// <remarks>For internal infrastructure only</remarks>
132 [MethodImpl(MethodImplOptions.AggressiveInlining)]
133 internal Memory(MemoryManager<T> manager, int length)
135 Debug.Assert(manager != null);
137 if (length < 0)
138 ThrowHelper.ThrowArgumentOutOfRangeException();
140 _object = manager;
141 _index = 0;
142 _length = length;
145 /// <summary>
146 /// Creates a new memory from a memory manager that provides specific method implementations beginning
147 /// at 'start' index and ending at 'end' index (exclusive).
148 /// </summary>
149 /// <param name="manager">The memory manager.</param>
150 /// <param name="start">The index at which to begin the memory.</param>
151 /// <param name="length">The number of items in the memory.</param>
152 /// <exception cref="System.ArgumentOutOfRangeException">
153 /// Thrown when the specified <paramref name="start"/> or <paramref name="length"/> is negative.
154 /// </exception>
155 /// <remarks>For internal infrastructure only</remarks>
156 [MethodImpl(MethodImplOptions.AggressiveInlining)]
157 internal Memory(MemoryManager<T> manager, int start, int length)
159 Debug.Assert(manager != null);
161 if (length < 0 || start < 0)
162 ThrowHelper.ThrowArgumentOutOfRangeException();
164 _object = manager;
165 _index = start;
166 _length = length;
169 [MethodImpl(MethodImplOptions.AggressiveInlining)]
170 internal Memory(object? obj, int start, int length)
172 // No validation performed in release builds; caller must provide any necessary validation.
174 // 'obj is T[]' below also handles things like int[] <-> uint[] being convertible
175 Debug.Assert((obj == null)
176 || (typeof(T) == typeof(char) && obj is string)
177 #if FEATURE_UTF8STRING
178 || ((typeof(T) == typeof(byte) || typeof(T) == typeof(Char8)) && obj is Utf8String)
179 #endif // FEATURE_UTF8STRING
180 || (obj is T[])
181 || (obj is MemoryManager<T>));
183 _object = obj;
184 _index = start;
185 _length = length;
188 /// <summary>
189 /// Defines an implicit conversion of an array to a <see cref="Memory{T}"/>
190 /// </summary>
191 public static implicit operator Memory<T>(T[]? array) => new Memory<T>(array);
193 /// <summary>
194 /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="Memory{T}"/>
195 /// </summary>
196 public static implicit operator Memory<T>(ArraySegment<T> segment) => new Memory<T>(segment.Array, segment.Offset, segment.Count);
198 /// <summary>
199 /// Defines an implicit conversion of a <see cref="Memory{T}"/> to a <see cref="ReadOnlyMemory{T}"/>
200 /// </summary>
201 public static implicit operator ReadOnlyMemory<T>(Memory<T> memory) =>
202 Unsafe.As<Memory<T>, ReadOnlyMemory<T>>(ref memory);
204 /// <summary>
205 /// Returns an empty <see cref="Memory{T}"/>
206 /// </summary>
207 public static Memory<T> Empty => default;
209 /// <summary>
210 /// The number of items in the memory.
211 /// </summary>
212 public int Length => _length;
214 /// <summary>
215 /// Returns true if Length is 0.
216 /// </summary>
217 public bool IsEmpty => _length == 0;
219 /// <summary>
220 /// For <see cref="Memory{Char}"/>, returns a new instance of string that represents the characters pointed to by the memory.
221 /// Otherwise, returns a <see cref="string"/> with the name of the type and the number of elements.
222 /// </summary>
223 public override string ToString()
225 if (typeof(T) == typeof(char))
227 return (_object is string str) ? str.Substring(_index, _length) : Span.ToString();
229 #if FEATURE_UTF8STRING
230 else if (typeof(T) == typeof(Char8))
232 // TODO_UTF8STRING: Call into optimized transcoding routine when it's available.
233 Span<T> span = Span;
234 return Encoding.UTF8.GetString(new ReadOnlySpan<byte>(ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)), span.Length));
236 #endif // FEATURE_UTF8STRING
237 return string.Format("System.Memory<{0}>[{1}]", typeof(T).Name, _length);
240 /// <summary>
241 /// Forms a slice out of the given memory, beginning at 'start'.
242 /// </summary>
243 /// <param name="start">The index at which to begin this slice.</param>
244 /// <exception cref="System.ArgumentOutOfRangeException">
245 /// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;Length).
246 /// </exception>
247 [MethodImpl(MethodImplOptions.AggressiveInlining)]
248 public Memory<T> Slice(int start)
250 if ((uint)start > (uint)_length)
252 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
255 // It is expected for _index + start to be negative if the memory is already pre-pinned.
256 return new Memory<T>(_object, _index + start, _length - start);
259 /// <summary>
260 /// Forms a slice out of the given memory, beginning at 'start', of given length
261 /// </summary>
262 /// <param name="start">The index at which to begin this slice.</param>
263 /// <param name="length">The desired length for the slice (exclusive).</param>
264 /// <exception cref="System.ArgumentOutOfRangeException">
265 /// Thrown when the specified <paramref name="start"/> or end index is not in range (&lt;0 or &gt;Length).
266 /// </exception>
267 [MethodImpl(MethodImplOptions.AggressiveInlining)]
268 public Memory<T> Slice(int start, int length)
270 #if BIT64
271 // See comment in Span<T>.Slice for how this works.
272 if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)_length)
273 ThrowHelper.ThrowArgumentOutOfRangeException();
274 #else
275 if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
276 ThrowHelper.ThrowArgumentOutOfRangeException();
277 #endif
279 // It is expected for _index + start to be negative if the memory is already pre-pinned.
280 return new Memory<T>(_object, _index + start, length);
283 /// <summary>
284 /// Returns a span from the memory.
285 /// </summary>
286 public unsafe Span<T> Span
288 [MethodImpl(MethodImplOptions.AggressiveInlining)]
291 // This property getter has special support for returning a mutable Span<char> that wraps
292 // an immutable String instance. This is obviously a dangerous feature and breaks type safety.
293 // However, we need to handle the case where a ReadOnlyMemory<char> was created from a string
294 // and then cast to a Memory<T>. Such a cast can only be done with unsafe or marshaling code,
295 // in which case that's the dangerous operation performed by the dev, and we're just following
296 // suit here to make it work as best as possible.
298 ref T refToReturn = ref Unsafe.AsRef<T>(null);
299 int lengthOfUnderlyingSpan = 0;
301 // Copy this field into a local so that it can't change out from under us mid-operation.
303 object? tmpObject = _object;
304 if (tmpObject != null)
306 if (typeof(T) == typeof(char) && tmpObject.GetType() == typeof(string))
308 // Special-case string since it's the most common for ROM<char>.
310 refToReturn = ref Unsafe.As<char, T>(ref Unsafe.As<string>(tmpObject).GetRawStringData());
311 lengthOfUnderlyingSpan = Unsafe.As<string>(tmpObject).Length;
313 #if FEATURE_UTF8STRING
314 else if ((typeof(T) == typeof(byte) || typeof(T) == typeof(Char8)) && tmpObject.GetType() == typeof(Utf8String))
316 refToReturn = ref Unsafe.As<byte, T>(ref Unsafe.As<Utf8String>(tmpObject).DangerousGetMutableReference());
317 lengthOfUnderlyingSpan = Unsafe.As<Utf8String>(tmpObject).Length;
319 #endif // FEATURE_UTF8STRING
320 else if (RuntimeHelpers.ObjectHasComponentSize(tmpObject))
322 // We know the object is not null, it's not a string, and it is variable-length. The only
323 // remaining option is for it to be a T[] (or a U[] which is blittable to T[], like int[]
324 // and uint[]). Otherwise somebody used private reflection to set this field, and we're not
325 // too worried about type safety violations at this point.
327 // 'tmpObject is T[]' below also handles things like int[] <-> uint[] being convertible
328 Debug.Assert(tmpObject is T[]);
330 refToReturn = ref Unsafe.As<byte, T>(ref Unsafe.As<T[]>(tmpObject).GetRawSzArrayData());
331 lengthOfUnderlyingSpan = Unsafe.As<T[]>(tmpObject).Length;
333 else
335 // We know the object is not null, and it's not variable-length, so it must be a MemoryManager<T>.
336 // Otherwise somebody used private reflection to set this field, and we're not too worried about
337 // type safety violations at that point. Note that it can't be a MemoryManager<U>, even if U and
338 // T are blittable (e.g., MemoryManager<int> to MemoryManager<uint>), since there exists no
339 // constructor or other public API which would allow such a conversion.
341 Debug.Assert(tmpObject is MemoryManager<T>);
342 Span<T> memoryManagerSpan = Unsafe.As<MemoryManager<T>>(tmpObject).GetSpan();
343 refToReturn = ref MemoryMarshal.GetReference(memoryManagerSpan);
344 lengthOfUnderlyingSpan = memoryManagerSpan.Length;
347 // If the Memory<T> or ReadOnlyMemory<T> instance is torn, this property getter has undefined behavior.
348 // We try to detect this condition and throw an exception, but it's possible that a torn struct might
349 // appear to us to be valid, and we'll return an undesired span. Such a span is always guaranteed at
350 // least to be in-bounds when compared with the original Memory<T> instance, so using the span won't
351 // AV the process.
353 nuint desiredStartIndex = (uint)_index & (uint)ReadOnlyMemory<T>.RemoveFlagsBitMask;
354 int desiredLength = _length;
356 #if BIT64
357 // See comment in Span<T>.Slice for how this works.
358 if ((ulong)desiredStartIndex + (ulong)(uint)desiredLength > (ulong)(uint)lengthOfUnderlyingSpan)
360 ThrowHelper.ThrowArgumentOutOfRangeException();
362 #else
363 if ((uint)desiredStartIndex > (uint)lengthOfUnderlyingSpan || (uint)desiredLength > (uint)(lengthOfUnderlyingSpan - desiredStartIndex))
365 ThrowHelper.ThrowArgumentOutOfRangeException();
367 #endif
369 refToReturn = ref Unsafe.Add(ref refToReturn, (IntPtr)(void*)desiredStartIndex);
370 lengthOfUnderlyingSpan = desiredLength;
373 return new Span<T>(ref refToReturn, lengthOfUnderlyingSpan);
377 /// <summary>
378 /// Copies the contents of the memory into the destination. If the source
379 /// and destination overlap, this method behaves as if the original values are in
380 /// a temporary location before the destination is overwritten.
382 /// <param name="destination">The Memory to copy items into.</param>
383 /// <exception cref="System.ArgumentException">
384 /// Thrown when the destination is shorter than the source.
385 /// </exception>
386 /// </summary>
387 public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
389 /// <summary>
390 /// Copies the contents of the memory into the destination. If the source
391 /// and destination overlap, this method behaves as if the original values are in
392 /// a temporary location before the destination is overwritten.
394 /// <returns>If the destination is shorter than the source, this method
395 /// return false and no data is written to the destination.</returns>
396 /// </summary>
397 /// <param name="destination">The span to copy items into.</param>
398 public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
400 /// <summary>
401 /// Creates a handle for the memory.
402 /// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
403 /// is disposed, enabling taking and using the memory's address.
404 /// <exception cref="System.ArgumentException">
405 /// An instance with nonprimitive (non-blittable) members cannot be pinned.
406 /// </exception>
407 /// </summary>
408 public unsafe MemoryHandle Pin()
410 // Just like the Span property getter, we have special support for a mutable Memory<char>
411 // that wraps an immutable String instance. This might happen if a caller creates an
412 // immutable ROM<char> wrapping a String, then uses Unsafe.As to create a mutable M<char>.
413 // This needs to work, however, so that code that uses a single Memory<char> field to store either
414 // a readable ReadOnlyMemory<char> or a writable Memory<char> can still be pinned and
415 // used for interop purposes.
417 // It's possible that the below logic could result in an AV if the struct
418 // is torn. This is ok since the caller is expecting to use raw pointers,
419 // and we're not required to keep this as safe as the other Span-based APIs.
421 object? tmpObject = _object;
422 if (tmpObject != null)
424 if (typeof(T) == typeof(char) && tmpObject is string s)
426 GCHandle handle = GCHandle.Alloc(tmpObject, GCHandleType.Pinned);
427 ref char stringData = ref Unsafe.Add(ref s.GetRawStringData(), _index);
428 return new MemoryHandle(Unsafe.AsPointer(ref stringData), handle);
430 #if FEATURE_UTF8STRING
431 else if ((typeof(T) == typeof(byte) || typeof(T) == typeof(Char8)) && tmpObject is Utf8String utf8String)
433 GCHandle handle = GCHandle.Alloc(tmpObject, GCHandleType.Pinned);
434 ref byte stringData = ref utf8String.DangerousGetMutableReference(_index);
435 return new MemoryHandle(Unsafe.AsPointer(ref stringData), handle);
437 #endif // FEATURE_UTF8STRING
438 else if (RuntimeHelpers.ObjectHasComponentSize(tmpObject))
440 // 'tmpObject is T[]' below also handles things like int[] <-> uint[] being convertible
441 Debug.Assert(tmpObject is T[]);
443 // Array is already pre-pinned
444 if (_index < 0)
446 void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref Unsafe.As<T[]>(tmpObject).GetRawSzArrayData()), _index & ReadOnlyMemory<T>.RemoveFlagsBitMask);
447 return new MemoryHandle(pointer);
449 else
451 GCHandle handle = GCHandle.Alloc(tmpObject, GCHandleType.Pinned);
452 void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref Unsafe.As<T[]>(tmpObject).GetRawSzArrayData()), _index);
453 return new MemoryHandle(pointer, handle);
456 else
458 Debug.Assert(tmpObject is MemoryManager<T>);
459 return Unsafe.As<MemoryManager<T>>(tmpObject).Pin(_index);
463 return default;
466 /// <summary>
467 /// Copies the contents from the memory into a new array. This heap
468 /// allocates, so should generally be avoided, however it is sometimes
469 /// necessary to bridge the gap with APIs written in terms of arrays.
470 /// </summary>
471 public T[] ToArray() => Span.ToArray();
473 /// <summary>
474 /// Determines whether the specified object is equal to the current object.
475 /// Returns true if the object is Memory or ReadOnlyMemory and if both objects point to the same array and have the same length.
476 /// </summary>
477 [EditorBrowsable(EditorBrowsableState.Never)]
478 public override bool Equals(object? obj)
480 if (obj is ReadOnlyMemory<T>)
482 return ((ReadOnlyMemory<T>)obj).Equals(this);
484 else if (obj is Memory<T> memory)
486 return Equals(memory);
488 else
490 return false;
494 /// <summary>
495 /// Returns true if the memory points to the same array and has the same length. Note that
496 /// this does *not* check to see if the *contents* are equal.
497 /// </summary>
498 public bool Equals(Memory<T> other)
500 return
501 _object == other._object &&
502 _index == other._index &&
503 _length == other._length;
506 /// <summary>
507 /// Serves as the default hash function.
508 /// </summary>
509 [EditorBrowsable(EditorBrowsableState.Never)]
510 public override int GetHashCode()
512 // We use RuntimeHelpers.GetHashCode instead of Object.GetHashCode because the hash
513 // code is based on object identity and referential equality, not deep equality (as common with string).
514 return (_object != null) ? HashCode.Combine(RuntimeHelpers.GetHashCode(_object), _index, _length) : 0;