diff --git a/src/Common/src/TypeSystem/IL/Stubs/UnsafeIntrinsics.cs b/src/Common/src/TypeSystem/IL/Stubs/UnsafeIntrinsics.cs index a99944c407a..6a42f7bffd3 100644 --- a/src/Common/src/TypeSystem/IL/Stubs/UnsafeIntrinsics.cs +++ b/src/Common/src/TypeSystem/IL/Stubs/UnsafeIntrinsics.cs @@ -29,6 +29,12 @@ public static MethodIL EmitIL(MethodDesc method) return new ILStubMethodIL(method, new byte[] { (byte)ILOpcode.ldarg_0, (byte)ILOpcode.ret }, Array.Empty(), null); case "AddByteOffset": return new ILStubMethodIL(method, new byte[] { (byte)ILOpcode.ldarg_0, (byte)ILOpcode.ldarg_1, (byte)ILOpcode.add, (byte)ILOpcode.ret }, Array.Empty(), null); + case "InitBlockUnaligned": + return new ILStubMethodIL(method, new byte[] { + (byte)ILOpcode.ldarg_0, (byte)ILOpcode.ldarg_1, (byte)ILOpcode.ldarg_2, + (byte)ILOpcode.prefix1, unchecked((byte)ILOpcode.unaligned), 0x01, + (byte)ILOpcode.prefix1, unchecked((byte)ILOpcode.initblk), + (byte)ILOpcode.ret }, Array.Empty(), null); } return null; diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems index 6ef7fc1681b..d56b7e1a1f6 100644 --- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems @@ -169,6 +169,7 @@ + @@ -338,6 +339,8 @@ + + diff --git a/src/System.Private.CoreLib/shared/System/ReadOnlySpan.cs b/src/System.Private.CoreLib/shared/System/ReadOnlySpan.cs new file mode 100644 index 00000000000..8ec431f6fba --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/ReadOnlySpan.cs @@ -0,0 +1,323 @@ +// 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.CompilerServices; +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)' + +namespace System +{ + /// + /// ReadOnlySpan 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 ReadOnlySpan + { + /// A byref or a native ptr. + private readonly ByReference _pointer; + /// The number of elements this ReadOnlySpan contains. +#if PROJECTN + [Bound] +#endif + private readonly int _length; + + /// + /// Creates a new read-only span over the entirety of the target array. + /// + /// The target array. + /// Thrown when is a null + /// reference (Nothing in Visual Basic). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan(T[] array) + { + if (array == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + _pointer = new ByReference(ref Unsafe.As(ref array.GetRawSzArrayData())); + _length = array.Length; + } + + /// + /// Creates a new read-only 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 read-only span. + /// Thrown when is a null + /// reference (Nothing in Visual Basic). + /// + /// Thrown when the specified is not in the range (<0 or >=Length). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan(T[] array, int start) + { + if (array == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + if ((uint)start > (uint)array.Length) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + _pointer = new ByReference(ref Unsafe.Add(ref Unsafe.As(ref array.GetRawSzArrayData()), start)); + _length = array.Length - start; + } + + /// + /// Creates a new read-only 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 read-only span. + /// The number of items in the read-only span. + /// Thrown when is a null + /// reference (Nothing in Visual Basic). + /// + /// Thrown when the specified or end index is not in the range (<0 or >=Length). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan(T[] array, int start, int length) + { + if (array == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + _pointer = new ByReference(ref Unsafe.Add(ref Unsafe.As(ref array.GetRawSzArrayData()), start)); + _length = length; + } + + /// + /// Creates a new read-only 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 ReadOnlySpan(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 read-only 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 ReadOnlySpan DangerousCreate(object obj, ref T objectData, int length) => new ReadOnlySpan(ref objectData, length); + + // Constructor for internal use only. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan(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. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T DangerousGetPinnableReference() + { + return ref _pointer.Value; + } + + /// + /// The number of items in the read-only span. + /// + public int Length => _length; + + /// + /// Returns true if Length is 0. + /// + public bool IsEmpty => _length == 0; + + /// + /// Returns the specified element of the read-only span. + /// + /// + /// + /// + /// Thrown when index less than 0 or index greater than or equal to Length + /// + public T this[int index] + { +#if PROJECTN + [BoundsChecking] + get + { + return Unsafe.Add(ref _pointer.Value, index); + } +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((uint)index >= (uint)_length) + ThrowHelper.ThrowIndexOutOfRangeException(); + return Unsafe.Add(ref _pointer.Value, index); + } +#endif + } + + /// + /// Copies the contents of this read-only 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 read-only 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. + /// + /// If the destination span is shorter than the source span, this method + /// return false and no data is written to the destination. + /// The span to copy items into. + public bool TryCopyTo(Span destination) + { + if ((uint)_length > (uint)destination.Length) + return false; + + Span.CopyTo(ref destination.DangerousGetPinnableReference(), 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 ==(ReadOnlySpan left, ReadOnlySpan 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 !=(ReadOnlySpan left, ReadOnlySpan 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 ReadOnlySpan(T[] array) => new ReadOnlySpan(array); + + /// + /// Defines an implicit conversion of a to a + /// + public static implicit operator ReadOnlySpan(ArraySegment arraySegment) => new ReadOnlySpan(arraySegment.Array, arraySegment.Offset, arraySegment.Count); + + /// + /// Forms a slice out of the given read-only 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 ReadOnlySpan Slice(int start) + { + if ((uint)start > (uint)_length) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + return new ReadOnlySpan(ref Unsafe.Add(ref _pointer.Value, start), _length - start); + } + + /// + /// Forms a slice out of the given read-only 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 ReadOnlySpan Slice(int start, int length) + { + if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + return new ReadOnlySpan(ref Unsafe.Add(ref _pointer.Value, start), length); + } + + /// + /// Copies the contents of this read-only 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. + /// + public T[] ToArray() + { + if (_length == 0) + return Array.Empty(); + + var destination = new T[_length]; + Span.CopyTo(ref Unsafe.As(ref destination.GetRawSzArrayData()), ref _pointer.Value, _length); + return destination; + } + + /// + /// Returns a 0-length read-only span whose base is the null pointer. + /// + public static ReadOnlySpan Empty => default(ReadOnlySpan); + } +} diff --git a/src/System.Private.CoreLib/shared/System/Span.NonGeneric.cs b/src/System.Private.CoreLib/shared/System/Span.NonGeneric.cs new file mode 100644 index 00000000000..4cdba2181a0 --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/Span.NonGeneric.cs @@ -0,0 +1,522 @@ +// 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; + +#if BIT64 +using nuint = System.UInt64; +#else +using nuint = System.UInt32; +#endif + +namespace System +{ + /// + /// Extension methods and non-generic helpers for Span and ReadOnlySpan + /// + 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.GetRawStringData(), text.Length); + } + + 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 && CORECLR + if (byteLength > 4096) goto PInvoke; + Unsafe.InitBlockUnaligned(ref b, 0, (uint)byteLength); + return; +#else + // TODO: Optimize other platforms to be on par with AMD64 CoreCLR + // 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 + + 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); + } + } + } +} diff --git a/src/System.Private.CoreLib/shared/System/Span.cs b/src/System.Private.CoreLib/shared/System/Span.cs new file mode 100644 index 00000000000..82ea35f6703 --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/Span.cs @@ -0,0 +1,413 @@ +// 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 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. +#if PROJECTN + [Bound] +#endif + 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 Unsafe.As(ref array.GetRawSzArrayData())); + _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 Unsafe.As(ref array.GetRawSzArrayData()), 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 Unsafe.As(ref array.GetRawSzArrayData()), 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] + { +#if PROJECTN + [BoundsChecking] + get + { + return ref Unsafe.Add(ref _pointer.Value, index); + } +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((uint)index >= (uint)_length) + ThrowHelper.ThrowIndexOutOfRangeException(); + return ref Unsafe.Add(ref _pointer.Value, index); + } +#endif + } + + /// + /// Clears the contents of this span. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + Span.ClearWithReferences(ref Unsafe.As(ref _pointer.Value), (nuint)_length * (nuint)(Unsafe.SizeOf() / sizeof(nuint))); + } + else + { + Span.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; + + Span.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]; + Span.CopyTo(ref Unsafe.As(ref destination.GetRawSzArrayData()), ref _pointer.Value, _length); + return destination; + } + + // + /// Returns an empty + /// + public static Span Empty => default(Span); + } +} diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs b/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs index 12bc670ac7e..09bed6e2c11 100644 --- a/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs +++ b/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs @@ -863,7 +863,7 @@ public static int GetCorElementType(RuntimeTypeHandle type) // correct write barrier such that the GC is not incorrectly impacted. public static unsafe void BulkMoveWithWriteBarrier(IntPtr dmem, IntPtr smem, int size) { - RuntimeImports.RhBulkMoveWithWriteBarrier((byte*)dmem.ToPointer(), (byte*)smem.ToPointer(), size); + RuntimeImports.RhBulkMoveWithWriteBarrier(ref *(byte*)dmem.ToPointer(), ref *(byte*)smem.ToPointer(), (uint)size); } public static IntPtr GetUniversalTransitionThunk() diff --git a/src/System.Private.CoreLib/src/Resources/Strings.resx b/src/System.Private.CoreLib/src/Resources/Strings.resx index 3f71486a5ef..df29191d374 100644 --- a/src/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/System.Private.CoreLib/src/Resources/Strings.resx @@ -2398,4 +2398,16 @@ '{0}' property specified was not found. + + Equals() on Span and ReadOnlySpan is not supported. Use operator== instead. + + + GetHashCode() on Span and ReadOnlySpan is not supported. + + + Destination is too short. + + + Cannot use type '{0}'. Only value types without pointers or references are supported. + diff --git a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj index f037a8601b9..00e8529b216 100644 --- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -430,6 +430,7 @@ + diff --git a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/InternalCompilerAttributes.cs b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/InternalCompilerAttributes.cs index 3ad095e9183..2de06e48749 100644 --- a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/InternalCompilerAttributes.cs +++ b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/InternalCompilerAttributes.cs @@ -11,7 +11,7 @@ namespace System.Runtime.CompilerServices [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field)] internal sealed class IntrinsicAttribute : Attribute { } -#if !CORERT +#if PROJECTN [AttributeUsage(AttributeTargets.Field)] internal sealed class BoundAttribute : Attribute { } diff --git a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs index f2f7e4a0bfd..4c38b38938d 100644 --- a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs +++ b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs @@ -22,12 +22,12 @@ namespace System.Runtime.CompilerServices /// /// Contains generic, low-level functionality for manipulating pointers. /// + [CLSCompliant(false)] public static class Unsafe { /// /// Reads a value of type from the given location. /// - [CLSCompliant(false)] [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe T Read(void* source) @@ -38,7 +38,6 @@ public static unsafe T Read(void* source) /// /// Writes a value of type to the given location. /// - [CLSCompliant(false)] [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void Write(void* source, T value) @@ -49,7 +48,6 @@ public static unsafe void Write(void* source, T value) /// /// Returns a pointer to the given by-ref parameter. /// - [CLSCompliant(false)] [Intrinsic] [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -108,6 +106,14 @@ public static ref TTo As(ref TFrom source) // ret } + [Intrinsic] + [NonVersionable] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe ref T AddByteOffset(ref T source, nuint byteOffset) + { + return ref AddByteOffset(ref source, (IntPtr)(void*)byteOffset); + } + [Intrinsic] [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -122,15 +128,6 @@ public static ref T AddByteOffset(ref T source, IntPtr byteOffset) // ret } - [Intrinsic] - [NonVersionable] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe ref T AddByteOffset(ref T source, nuint byteOffset) - { - return ref AddByteOffset(ref source, (IntPtr)(void*)byteOffset); - } - - /// /// Adds an element offset to the given reference. /// @@ -157,5 +154,18 @@ public static bool AreSame(ref T left, ref T right) // ceq // ret } + + /// + /// Initializes a block of memory at the given location with a given initial value + /// without assuming architecture dependent alignment of the address. + /// + [Intrinsic] + [NonVersionable] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitBlockUnaligned(ref byte startAddress, byte value, uint byteCount) + { + for (uint i = 0; i < byteCount; i++) + AddByteOffset(ref startAddress, i) = value; + } } } diff --git a/src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index 0ce0915fb4e..77e7c295860 100644 --- a/src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -8,6 +8,12 @@ using System.Runtime.CompilerServices; using Internal.Runtime; +#if BIT64 +using nuint = System.UInt64; +#else +using nuint = System.UInt32; +#endif + namespace System.Runtime { // CONTRACT with Runtime @@ -636,7 +642,7 @@ internal static uint RhGetLoadedModules(TypeManagerHandle[] resultArray) // heap memory [MethodImplAttribute(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhBulkMoveWithWriteBarrier")] - internal static extern unsafe void RhBulkMoveWithWriteBarrier(byte* dmem, byte* smem, int size); + internal static extern unsafe void RhBulkMoveWithWriteBarrier(ref byte dmem, ref byte smem, nuint size); // The GC conservative reporting descriptor is a special structure of data that the GC // parses to determine whether there are specific regions of memory that it should not @@ -844,13 +850,21 @@ internal static byte[] ConvertPublicKeyToPublicKeyToken(byte[] publicKey) internal static extern unsafe void _ecvt_s(byte* buffer, int sizeInBytes, double value, int count, int* dec, int* sign); #endif -#if BIT64 [DllImport(RuntimeImports.RuntimeLibrary, ExactSpelling = true)] - internal static extern unsafe void memmove(byte* dmem, byte* smem, ulong size); -#else + internal static extern unsafe void memmove(byte* dmem, byte* smem, nuint size); + [DllImport(RuntimeImports.RuntimeLibrary, ExactSpelling = true)] - internal static extern unsafe void memmove(byte* dmem, byte* smem, uint size); -#endif + internal static extern unsafe void memset(byte* mem, int value, nuint size); + + // Non-inlinable wrapper around the PInvoke that avoids poluting the fast path with P/Invoke prolog/epilog. + [MethodImpl(MethodImplOptions.NoInlining)] + internal unsafe static void RhZeroMemory(ref byte b, nuint byteLength) + { + fixed (byte* bytePointer = &b) + { + memset(bytePointer, 0, byteLength); + } + } [MethodImpl(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhpArrayCopy")] diff --git a/src/System.Private.CoreLib/src/System/String.cs b/src/System.Private.CoreLib/src/System/String.cs index ba7f325b9a4..1276ce1cae2 100644 --- a/src/System.Private.CoreLib/src/System/String.cs +++ b/src/System.Private.CoreLib/src/System/String.cs @@ -87,7 +87,7 @@ public sealed partial class String : IComparable, IEnumerable, IEnumerable // CS0649: Field '{blah}' is never assigned to, and will always have its default value #pragma warning disable 169, 649 -#if !CORERT +#if PROJECTN [Bound] #endif // WARNING: We allow diagnostic tools to directly inspect these two members (_stringLength, _firstChar) @@ -423,19 +423,18 @@ public static unsafe String Copy(String str) [System.Runtime.CompilerServices.IndexerName("Chars")] public unsafe char this[int index] { - [NonVersionable] -#if CORERT - [Intrinsic] +#if PROJECTN + [BoundsChecking] get { - if ((uint)index >= _stringLength) - throw new IndexOutOfRangeException(); return Unsafe.Add(ref _firstChar, index); } #else - [BoundsChecking] + [Intrinsic] get { + if ((uint)index >= _stringLength) + ThrowHelper.ThrowIndexOutOfRangeException(); return Unsafe.Add(ref _firstChar, index); } #endif @@ -539,6 +538,11 @@ public int Length get { return _stringLength; } } + internal ref char GetRawStringData() + { + return ref _firstChar; + } + // Helper for encodings so they can talk to our buffer directly // stringLength must be the exact size we'll expect unsafe internal static String CreateStringFromEncoding( diff --git a/src/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/System.Private.CoreLib/src/System/ThrowHelper.cs new file mode 100644 index 00000000000..7588e65ecb4 --- /dev/null +++ b/src/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -0,0 +1,104 @@ +// 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. + + +// This file defines an internal class used to throw exceptions in BCL code. +// The main purpose is to reduce code size. +// +// The old way to throw an exception generates quite a lot IL code and assembly code. +// Following is an example: +// C# source +// throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key); +// IL code: +// IL_0003: ldstr "key" +// IL_0008: ldstr "ArgumentNull_Key" +// IL_000d: call string System.Environment::GetResourceString(string) +// IL_0012: newobj instance void System.ArgumentNullException::.ctor(string,string) +// IL_0017: throw +// which is 21bytes in IL. +// +// So we want to get rid of the ldstr and call to Environment.GetResource in IL. +// In order to do that, I created two enums: ExceptionResource, ExceptionArgument to represent the +// argument name and resource name in a small integer. The source code will be changed to +// ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key, ExceptionResource.ArgumentNull_Key); +// +// The IL code will be 7 bytes. +// IL_0008: ldc.i4.4 +// IL_0009: ldc.i4.4 +// IL_000a: call void System.ThrowHelper::ThrowArgumentNullException(valuetype System.ExceptionArgument) +// IL_000f: ldarg.0 +// +// This will also reduce the Jitted code size a lot. +// +// It is very important we do this for generic classes because we can easily generate the same code +// multiple times for different instantiation. +// + +using System.Diagnostics; +using System.Diagnostics.Contracts; + +namespace System +{ + [Pure] + internal static class ThrowHelper + { + internal static void ThrowArrayTypeMismatchException() + { + throw new ArrayTypeMismatchException(); + } + + internal static void ThrowInvalidTypeWithPointersNotSupported(Type targetType) + { + throw new ArgumentException(SR.Format(SR.Argument_InvalidTypeWithPointersNotSupported, targetType)); + } + + internal static void ThrowIndexOutOfRangeException() + { + throw new IndexOutOfRangeException(); + } + + internal static void ThrowArgumentOutOfRangeException() + { + throw new ArgumentOutOfRangeException(); + } + + internal static void ThrowArgumentException_DestinationTooShort() + { + throw new ArgumentException(SR.Argument_DestinationTooShort); + } + + internal static void ThrowArgumentNullException(ExceptionArgument argument) + { + throw GetArgumentNullException(argument); + } + private static ArgumentNullException GetArgumentNullException(ExceptionArgument argument) + { + return new ArgumentNullException(GetArgumentName(argument)); + } + + private static string GetArgumentName(ExceptionArgument argument) + { + switch (argument) + { + case ExceptionArgument.array: + return "array"; + case ExceptionArgument.text: + return "text"; + default: + Debug.Assert(false, + "The enum value is not defined, please check the ExceptionArgument Enum."); + return ""; + } + } + } + + // + // The convention for this enum is using the argument name as the enum name + // + internal enum ExceptionArgument + { + array, + text, + } +} diff --git a/src/packaging/netcoreapp/project.json b/src/packaging/netcoreapp/project.json index 18b903e637f..70d20069031 100644 --- a/src/packaging/netcoreapp/project.json +++ b/src/packaging/netcoreapp/project.json @@ -4,6 +4,7 @@ "dependencies": { "Microsoft.Private.CoreFx.NETCoreApp": "4.4.0-preview1-25210-01", + "System.Memory": "4.4.0-preview1-25210-01", "System.Runtime.CompilerServices.Unsafe": "4.4.0-preview1-25210-01", "Microsoft.NETCore.Native": "2.0.0-beta-25021-03" diff --git a/src/packaging/packages.targets b/src/packaging/packages.targets index b39bfbc8162..d7d230de664 100644 --- a/src/packaging/packages.targets +++ b/src/packaging/packages.targets @@ -180,6 +180,7 @@ ]]> +