// 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.Diagnostics; using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using EditorBrowsableState = System.ComponentModel.EditorBrowsableState; using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute; #pragma warning disable 0809 //warning CS0809: Obsolete member 'Span.Equals(object)' overrides non-obsolete member 'object.Equals(object)' #if BIT64 using nuint = System.UInt64; #else using nuint = System.UInt32; #endif namespace System { /// /// Span represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed /// or native memory, or to memory allocated on the stack. It is type- and memory-safe. /// public struct Span { /// A byref or a native ptr. private readonly ByReference _pointer; /// The number of elements this Span contains. private readonly int _length; /// /// Creates a new span over the entirety of the target array. /// /// The target array. /// Thrown when is a null /// reference (Nothing in Visual Basic). /// Thrown when is covariant and array's type is not exactly T[]. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span(T[] array) { if (array == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) ThrowHelper.ThrowArrayTypeMismatchException(); _pointer = new ByReference(ref JitHelpers.GetArrayData(array)); _length = array.Length; } /// /// Creates a new span over the portion of the target array beginning /// at 'start' index and covering the remainder of the array. /// /// The target array. /// The index at which to begin the span. /// Thrown when is a null /// reference (Nothing in Visual Basic). /// Thrown when is covariant and array's type is not exactly T[]. /// /// Thrown when the specified is not in the range (<0 or >=Length). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span(T[] array, int start) { if (array == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) ThrowHelper.ThrowArrayTypeMismatchException(); if ((uint)start > (uint)array.Length) ThrowHelper.ThrowArgumentOutOfRangeException(); _pointer = new ByReference(ref Unsafe.Add(ref JitHelpers.GetArrayData(array), start)); _length = array.Length - start; } /// /// Creates a new span 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 span. /// The number of items in the span. /// Thrown when is a null /// reference (Nothing in Visual Basic). /// 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 Span(T[] array, int start, int length) { if (array == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) ThrowHelper.ThrowArrayTypeMismatchException(); if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) ThrowHelper.ThrowArgumentOutOfRangeException(); _pointer = new ByReference(ref Unsafe.Add(ref JitHelpers.GetArrayData(array), start)); _length = length; } /// /// Creates a new span over the target unmanaged buffer. Clearly this /// is quite dangerous, because we are creating arbitrarily typed T's /// out of a void*-typed block of memory. And the length is not checked. /// But if this creation is correct, then all subsequent uses are correct. /// /// An unmanaged pointer to memory. /// The number of elements the memory contains. /// /// Thrown when is reference type or contains pointers and hence cannot be stored in unmanaged memory. /// /// /// Thrown when the specified is negative. /// [CLSCompliant(false)] [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe Span(void* pointer, int length) { if (RuntimeHelpers.IsReferenceOrContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); if (length < 0) ThrowHelper.ThrowArgumentOutOfRangeException(); _pointer = new ByReference(ref Unsafe.As(ref *(byte*)pointer)); _length = length; } /// /// Create a new span over a portion of a regular managed object. This can be useful /// if part of a managed object represents a "fixed array." This is dangerous because neither the /// is checked, nor being null, nor the fact that /// "rawPointer" actually lies within . /// /// The managed object that contains the data to span over. /// A reference to data within that object. /// The number of elements the memory contains. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span DangerousCreate(object obj, ref T objectData, int length) => new Span(ref objectData, length); // Constructor for internal use only. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Span(ref T ptr, int length) { Debug.Assert(length >= 0); _pointer = new ByReference(ref ptr); _length = length; } /// /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element /// would have been stored. Such a reference can be used for pinning but must never be dereferenced. /// public ref T DangerousGetPinnableReference() { return ref _pointer.Value; } /// /// The number of items in the span. /// public int Length => _length; /// /// Returns true if Length is 0. /// public bool IsEmpty => _length == 0; /// /// Returns a reference to specified element of the Span. /// /// /// /// /// Thrown when index less than 0 or index greater than or equal to Length /// public ref T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if ((uint)index >= (uint)_length) ThrowHelper.ThrowIndexOutOfRangeException(); return ref Unsafe.Add(ref _pointer.Value, index); } } /// /// Clears the contents of this span. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { if (RuntimeHelpers.IsReferenceOrContainsReferences()) { SpanHelper.ClearWithReferences(ref Unsafe.As(ref _pointer.Value), (nuint)_length * (nuint)(Unsafe.SizeOf() / sizeof(nuint))); } else { SpanHelper.ClearWithoutReferences(ref Unsafe.As(ref _pointer.Value), (nuint)_length * (nuint)Unsafe.SizeOf()); } } /// /// Fills the contents of this span with the given value. /// public void Fill(T value) { if (Unsafe.SizeOf() == 1) { uint length = (uint)_length; if (length == 0) return; T tmp = value; // Avoid taking address of the "value" argument. It would regress performance of the loop below. Unsafe.InitBlockUnaligned(ref Unsafe.As(ref _pointer.Value), Unsafe.As(ref tmp), length); } else { // Do all math as nuint to avoid unnecessary 64->32->64 bit integer truncations nuint length = (uint)_length; if (length == 0) return; ref T r = ref DangerousGetPinnableReference(); // TODO: Create block fill for value types of power of two sizes e.g. 2,4,8,16 nuint elementSize = (uint)Unsafe.SizeOf(); nuint i = 0; for (; i < (length & ~(nuint)7); i += 8) { Unsafe.AddByteOffset(ref r, (i + 0) * elementSize) = value; Unsafe.AddByteOffset(ref r, (i + 1) * elementSize) = value; Unsafe.AddByteOffset(ref r, (i + 2) * elementSize) = value; Unsafe.AddByteOffset(ref r, (i + 3) * elementSize) = value; Unsafe.AddByteOffset(ref r, (i + 4) * elementSize) = value; Unsafe.AddByteOffset(ref r, (i + 5) * elementSize) = value; Unsafe.AddByteOffset(ref r, (i + 6) * elementSize) = value; Unsafe.AddByteOffset(ref r, (i + 7) * elementSize) = value; } if (i < (length & ~(nuint)3)) { Unsafe.AddByteOffset(ref r, (i + 0) * elementSize) = value; Unsafe.AddByteOffset(ref r, (i + 1) * elementSize) = value; Unsafe.AddByteOffset(ref r, (i + 2) * elementSize) = value; Unsafe.AddByteOffset(ref r, (i + 3) * elementSize) = value; i += 4; } for (; i < length; i++) { Unsafe.AddByteOffset(ref r, i * elementSize) = value; } } } /// /// Copies the contents of this span into destination span. If the source /// and destinations overlap, this method behaves as if the original values in /// a temporary location before the destination is overwritten. /// /// The span to copy items into. /// /// Thrown when the destination Span is shorter than the source Span. /// public void CopyTo(Span destination) { if (!TryCopyTo(destination)) ThrowHelper.ThrowArgumentException_DestinationTooShort(); } /// /// Copies the contents of this span into destination span. If the source /// and destinations overlap, this method behaves as if the original values in /// a temporary location before the destination is overwritten. /// /// The span to copy items into. /// If the destination span is shorter than the source span, this method /// return false and no data is written to the destination. public bool TryCopyTo(Span destination) { if ((uint)_length > (uint)destination.Length) return false; SpanHelper.CopyTo(ref destination._pointer.Value, ref _pointer.Value, _length); return true; } /// /// Returns true if left and right point at the same memory and have the same length. Note that /// this does *not* check to see if the *contents* are equal. /// public static bool operator ==(Span left, Span right) { return left._length == right._length && Unsafe.AreSame(ref left._pointer.Value, ref right._pointer.Value); } /// /// Returns false if left and right point at the same memory and have the same length. Note that /// this does *not* check to see if the *contents* are equal. /// public static bool operator !=(Span left, Span right) => !(left == right); /// /// This method is not supported as spans cannot be boxed. To compare two spans, use operator==. /// /// Always thrown by this method. /// /// [Obsolete("Equals() on Span will always throw an exception. Use == instead.")] [EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object obj) { throw new NotSupportedException(SR.NotSupported_CannotCallEqualsOnSpan); } /// /// This method is not supported as spans cannot be boxed. /// /// Always thrown by this method. /// /// [Obsolete("GetHashCode() on Span will always throw an exception.")] [EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() { throw new NotSupportedException(SR.NotSupported_CannotCallGetHashCodeOnSpan); } /// /// Defines an implicit conversion of an array to a /// public static implicit operator Span(T[] array) => new Span(array); /// /// Defines an implicit conversion of a to a /// public static implicit operator Span(ArraySegment arraySegment) => new Span(arraySegment.Array, arraySegment.Offset, arraySegment.Count); /// /// Defines an implicit conversion of a to a /// public static implicit operator ReadOnlySpan(Span span) => new ReadOnlySpan(ref span._pointer.Value, span._length); /// /// Forms a slice out of the given span, 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 Span Slice(int start) { if ((uint)start > (uint)_length) ThrowHelper.ThrowArgumentOutOfRangeException(); return new Span(ref Unsafe.Add(ref _pointer.Value, start), _length - start); } /// /// Forms a slice out of the given span, 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 Span Slice(int start, int length) { if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) ThrowHelper.ThrowArgumentOutOfRangeException(); return new Span(ref Unsafe.Add(ref _pointer.Value, start), length); } /// /// Copies the contents of this span 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. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public T[] ToArray() { if (_length == 0) return Array.Empty(); var destination = new T[_length]; SpanHelper.CopyTo(ref JitHelpers.GetArrayData(destination), ref _pointer.Value, _length); return destination; } // /// Returns an empty /// public static Span Empty => default(Span); } public static class Span { /// /// Casts a Span of one primitive type to Span of bytes. /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety. /// /// The source slice, of type . /// /// Thrown when contains pointers. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span AsBytes(this Span source) where T : struct { if (RuntimeHelpers.IsReferenceOrContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); return new Span( ref Unsafe.As(ref source.DangerousGetPinnableReference()), checked(source.Length * Unsafe.SizeOf())); } /// /// Casts a ReadOnlySpan of one primitive type to ReadOnlySpan of bytes. /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety. /// /// The source slice, of type . /// /// Thrown when contains pointers. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ReadOnlySpan AsBytes(this ReadOnlySpan source) where T : struct { if (RuntimeHelpers.IsReferenceOrContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); return new ReadOnlySpan( ref Unsafe.As(ref source.DangerousGetPinnableReference()), checked(source.Length * Unsafe.SizeOf())); } /// /// Casts a Span of one primitive type to another primitive type . /// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety. /// /// /// Supported only for platforms that support misaligned memory access. /// /// The source slice, of type . /// /// Thrown when or contains pointers. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span NonPortableCast(this Span source) where TFrom : struct where TTo : struct { if (RuntimeHelpers.IsReferenceOrContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TFrom)); if (RuntimeHelpers.IsReferenceOrContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo)); return new Span( ref Unsafe.As(ref source.DangerousGetPinnableReference()), checked((int)((long)source.Length * Unsafe.SizeOf() / Unsafe.SizeOf()))); } /// /// Casts a ReadOnlySpan of one primitive type to another primitive type . /// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety. /// /// /// Supported only for platforms that support misaligned memory access. /// /// The source slice, of type . /// /// Thrown when or contains pointers. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ReadOnlySpan NonPortableCast(this ReadOnlySpan source) where TFrom : struct where TTo : struct { if (RuntimeHelpers.IsReferenceOrContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TFrom)); if (RuntimeHelpers.IsReferenceOrContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo)); return new ReadOnlySpan( ref Unsafe.As(ref source.DangerousGetPinnableReference()), checked((int)((long)source.Length * Unsafe.SizeOf() / Unsafe.SizeOf()))); } /// /// Creates a new readonly span over the portion of the target string. /// /// The target string. /// Thrown when is a null /// reference (Nothing in Visual Basic). [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ReadOnlySpan AsSpan(this string text) { if (text == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text); return new ReadOnlySpan(ref text.GetFirstCharRef(), text.Length); } } internal static class SpanHelper { internal static unsafe void CopyTo(ref T destination, ref T source, int elementsCount) { if (Unsafe.AreSame(ref destination, ref source)) return; if (elementsCount <= 1) { if (elementsCount == 1) { destination = source; } return; } nuint byteCount = (nuint)elementsCount * (nuint)Unsafe.SizeOf(); if (!RuntimeHelpers.IsReferenceOrContainsReferences()) { fixed (byte* pDestination = &Unsafe.As(ref destination)) { fixed (byte* pSource = &Unsafe.As(ref source)) { Buffer.Memmove(pDestination, pSource, byteCount); } } } else { RuntimeImports.RhBulkMoveWithWriteBarrier( ref Unsafe.As(ref destination), ref Unsafe.As(ref source), byteCount); } } internal static unsafe void ClearWithoutReferences(ref byte b, nuint byteLength) { if (byteLength == 0) return; #if AMD64 if (byteLength > 4096) goto PInvoke; Unsafe.InitBlockUnaligned(ref b, 0, (uint)byteLength); return; #else // AMD64 // TODO: Optimize this method on X86 machine // Note: It's important that this switch handles lengths at least up to 22. // See notes below near the main loop for why. // The switch will be very fast since it can be implemented using a jump // table in assembly. See http://stackoverflow.com/a/449297/4077294 for more info. switch (byteLength) { case 1: b = 0; return; case 2: Unsafe.As(ref b) = 0; return; case 3: Unsafe.As(ref b) = 0; Unsafe.Add(ref b, 2) = 0; return; case 4: Unsafe.As(ref b) = 0; return; case 5: Unsafe.As(ref b) = 0; Unsafe.Add(ref b, 4) = 0; return; case 6: Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; return; case 7: Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; Unsafe.Add(ref b, 6) = 0; return; case 8: #if BIT64 Unsafe.As(ref b) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; #endif return; case 9: #if BIT64 Unsafe.As(ref b) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; #endif Unsafe.Add(ref b, 8) = 0; return; case 10: #if BIT64 Unsafe.As(ref b) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; #endif Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; return; case 11: #if BIT64 Unsafe.As(ref b) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; #endif Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; Unsafe.Add(ref b, 10) = 0; return; case 12: #if BIT64 Unsafe.As(ref b) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; #endif Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; return; case 13: #if BIT64 Unsafe.As(ref b) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; #endif Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; Unsafe.Add(ref b, 12) = 0; return; case 14: #if BIT64 Unsafe.As(ref b) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; #endif Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 12)) = 0; return; case 15: #if BIT64 Unsafe.As(ref b) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; #endif Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 12)) = 0; Unsafe.Add(ref b, 14) = 0; return; case 16: #if BIT64 Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 12)) = 0; #endif return; case 17: #if BIT64 Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 12)) = 0; #endif Unsafe.Add(ref b, 16) = 0; return; case 18: #if BIT64 Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 12)) = 0; #endif Unsafe.As(ref Unsafe.Add(ref b, 16)) = 0; return; case 19: #if BIT64 Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 12)) = 0; #endif Unsafe.As(ref Unsafe.Add(ref b, 16)) = 0; Unsafe.Add(ref b, 18) = 0; return; case 20: #if BIT64 Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 12)) = 0; #endif Unsafe.As(ref Unsafe.Add(ref b, 16)) = 0; return; case 21: #if BIT64 Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 12)) = 0; #endif Unsafe.As(ref Unsafe.Add(ref b, 16)) = 0; Unsafe.Add(ref b, 20) = 0; return; case 22: #if BIT64 Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; #else Unsafe.As(ref b) = 0; Unsafe.As(ref Unsafe.Add(ref b, 4)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 8)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 12)) = 0; #endif Unsafe.As(ref Unsafe.Add(ref b, 16)) = 0; Unsafe.As(ref Unsafe.Add(ref b, 20)) = 0; return; } // P/Invoke into the native version for large lengths if (byteLength >= 512) goto PInvoke; nuint i = 0; // byte offset at which we're copying if ((Unsafe.As(ref b) & 3) != 0) { if ((Unsafe.As(ref b) & 1) != 0) { Unsafe.AddByteOffset(ref b, i) = 0; i += 1; if ((Unsafe.As(ref b) & 2) != 0) goto IntAligned; } Unsafe.As(ref Unsafe.AddByteOffset(ref b, i)) = 0; i += 2; } IntAligned: // On 64-bit IntPtr.Size == 8, so we want to advance to the next 8-aligned address. If // (int)b % 8 is 0, 5, 6, or 7, we will already have advanced by 0, 3, 2, or 1 // bytes to the next aligned address (respectively), so do nothing. On the other hand, // if it is 1, 2, 3, or 4 we will want to copy-and-advance another 4 bytes until // we're aligned. // The thing 1, 2, 3, and 4 have in common that the others don't is that if you // subtract one from them, their 3rd lsb will not be set. Hence, the below check. if (((Unsafe.As(ref b) - 1) & 4) == 0) { Unsafe.As(ref Unsafe.AddByteOffset(ref b, i)) = 0; i += 4; } nuint end = byteLength - 16; byteLength -= i; // lower 4 bits of byteLength represent how many bytes are left *after* the unrolled loop // We know due to the above switch-case that this loop will always run 1 iteration; max // bytes we clear before checking is 23 (7 to align the pointers, 16 for 1 iteration) so // the switch handles lengths 0-22. Debug.Assert(end >= 7 && i <= end); // This is separated out into a different variable, so the i + 16 addition can be // performed at the start of the pipeline and the loop condition does not have // a dependency on the writes. nuint counter; do { counter = i + 16; // This loop looks very costly since there appear to be a bunch of temporary values // being created with the adds, but the jit (for x86 anyways) will convert each of // these to use memory addressing operands. // So the only cost is a bit of code size, which is made up for by the fact that // we save on writes to b. #if BIT64 Unsafe.As(ref Unsafe.AddByteOffset(ref b, i)) = 0; Unsafe.As(ref Unsafe.AddByteOffset(ref b, i + 8)) = 0; #else Unsafe.As(ref Unsafe.AddByteOffset(ref b, i)) = 0; Unsafe.As(ref Unsafe.AddByteOffset(ref b, i + 4)) = 0; Unsafe.As(ref Unsafe.AddByteOffset(ref b, i + 8)) = 0; Unsafe.As(ref Unsafe.AddByteOffset(ref b, i + 12)) = 0; #endif i = counter; // See notes above for why this wasn't used instead // i += 16; } while (counter <= end); if ((byteLength & 8) != 0) { #if BIT64 Unsafe.As(ref Unsafe.AddByteOffset(ref b, i)) = 0; #else Unsafe.As(ref Unsafe.AddByteOffset(ref b, i)) = 0; Unsafe.As(ref Unsafe.AddByteOffset(ref b, i + 4)) = 0; #endif i += 8; } if ((byteLength & 4) != 0) { Unsafe.As(ref Unsafe.AddByteOffset(ref b, i)) = 0; i += 4; } if ((byteLength & 2) != 0) { Unsafe.As(ref Unsafe.AddByteOffset(ref b, i)) = 0; i += 2; } if ((byteLength & 1) != 0) { Unsafe.AddByteOffset(ref b, i) = 0; // We're not using i after this, so not needed // i += 1; } return; #endif // AMD64 PInvoke: RuntimeImports.RhZeroMemory(ref b, byteLength); } internal static unsafe void ClearWithReferences(ref IntPtr ip, nuint pointerSizeLength) { if (pointerSizeLength == 0) return; // TODO: Perhaps do switch casing to improve small size perf nuint i = 0; nuint n = 0; while ((n = i + 8) <= (pointerSizeLength)) { Unsafe.AddByteOffset(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr); Unsafe.AddByteOffset(ref ip, (i + 1) * (nuint)sizeof(IntPtr)) = default(IntPtr); Unsafe.AddByteOffset(ref ip, (i + 2) * (nuint)sizeof(IntPtr)) = default(IntPtr); Unsafe.AddByteOffset(ref ip, (i + 3) * (nuint)sizeof(IntPtr)) = default(IntPtr); Unsafe.AddByteOffset(ref ip, (i + 4) * (nuint)sizeof(IntPtr)) = default(IntPtr); Unsafe.AddByteOffset(ref ip, (i + 5) * (nuint)sizeof(IntPtr)) = default(IntPtr); Unsafe.AddByteOffset(ref ip, (i + 6) * (nuint)sizeof(IntPtr)) = default(IntPtr); Unsafe.AddByteOffset(ref ip, (i + 7) * (nuint)sizeof(IntPtr)) = default(IntPtr); i = n; } if ((n = i + 4) <= (pointerSizeLength)) { Unsafe.AddByteOffset(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr); Unsafe.AddByteOffset(ref ip, (i + 1) * (nuint)sizeof(IntPtr)) = default(IntPtr); Unsafe.AddByteOffset(ref ip, (i + 2) * (nuint)sizeof(IntPtr)) = default(IntPtr); Unsafe.AddByteOffset(ref ip, (i + 3) * (nuint)sizeof(IntPtr)) = default(IntPtr); i = n; } if ((n = i + 2) <= (pointerSizeLength)) { Unsafe.AddByteOffset(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr); Unsafe.AddByteOffset(ref ip, (i + 1) * (nuint)sizeof(IntPtr)) = default(IntPtr); i = n; } if ((i + 1) <= (pointerSizeLength)) { Unsafe.AddByteOffset(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr); } } } }