From 61f6e6e58aee057b873703bdc58347e2e8b84867 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Thu, 10 Nov 2016 17:16:18 -0800 Subject: [PATCH 01/26] Rebase to make review readable again. --- .../packageIndex.json | 13 +- pkg/descriptions.json | 9 +- src/System.Memory/System.Memory.sln | 27 ++ src/System.Memory/pkg/System.Memory.builds | 9 + src/System.Memory/pkg/System.Memory.pkgproj | 10 + src/System.Memory/src/Resources/Strings.resx | 141 ++++++ src/System.Memory/src/System.Memory.builds | 9 + src/System.Memory/src/System.Memory.csproj | 25 ++ ...nableReferenceHelpers.Layouts.Microsoft.cs | 35 ++ .../src/System/PinnableReferenceHelpers.cs | 46 ++ src/System.Memory/src/System/Span.cs | 424 ++++++++++++++++++ src/System.Memory/src/System/SpanHelpers.cs | 97 ++++ src/System.Memory/src/project.json | 13 + src/System.Memory/tests/Span/CopyTo.cs | 87 ++++ src/System.Memory/tests/Span/CtorArray.cs | 121 +++++ src/System.Memory/tests/Span/CtorArrayInt.cs | 54 +++ .../tests/Span/CtorArrayIntInt.cs | 80 ++++ .../tests/Span/CtorPointerInt.cs | 78 ++++ .../tests/Span/DangerousCreate.cs | 74 +++ .../Span/DangerousGetPinnableReference.cs | 68 +++ src/System.Memory/tests/Span/Empty.cs | 26 ++ src/System.Memory/tests/Span/Equality.cs | 93 ++++ src/System.Memory/tests/Span/Overflow.cs | 64 +++ src/System.Memory/tests/Span/Slice.cs | 70 +++ src/System.Memory/tests/Span/TestHelpers.cs | 60 +++ src/System.Memory/tests/Span/ToArray.cs | 30 ++ .../tests/System.Memory.Tests.builds | 16 + .../tests/System.Memory.Tests.csproj | 32 ++ 28 files changed, 1809 insertions(+), 2 deletions(-) create mode 100644 src/System.Memory/System.Memory.sln create mode 100644 src/System.Memory/pkg/System.Memory.builds create mode 100644 src/System.Memory/pkg/System.Memory.pkgproj create mode 100644 src/System.Memory/src/Resources/Strings.resx create mode 100644 src/System.Memory/src/System.Memory.builds create mode 100644 src/System.Memory/src/System.Memory.csproj create mode 100644 src/System.Memory/src/System/PinnableReferenceHelpers.Layouts.Microsoft.cs create mode 100644 src/System.Memory/src/System/PinnableReferenceHelpers.cs create mode 100644 src/System.Memory/src/System/Span.cs create mode 100644 src/System.Memory/src/System/SpanHelpers.cs create mode 100644 src/System.Memory/src/project.json create mode 100644 src/System.Memory/tests/Span/CopyTo.cs create mode 100644 src/System.Memory/tests/Span/CtorArray.cs create mode 100644 src/System.Memory/tests/Span/CtorArrayInt.cs create mode 100644 src/System.Memory/tests/Span/CtorArrayIntInt.cs create mode 100644 src/System.Memory/tests/Span/CtorPointerInt.cs create mode 100644 src/System.Memory/tests/Span/DangerousCreate.cs create mode 100644 src/System.Memory/tests/Span/DangerousGetPinnableReference.cs create mode 100644 src/System.Memory/tests/Span/Empty.cs create mode 100644 src/System.Memory/tests/Span/Equality.cs create mode 100644 src/System.Memory/tests/Span/Overflow.cs create mode 100644 src/System.Memory/tests/Span/Slice.cs create mode 100644 src/System.Memory/tests/Span/TestHelpers.cs create mode 100644 src/System.Memory/tests/Span/ToArray.cs create mode 100644 src/System.Memory/tests/System.Memory.Tests.builds create mode 100644 src/System.Memory/tests/System.Memory.Tests.csproj diff --git a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json index c011be9f128a..a3e6e5d0f5b8 100644 --- a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json +++ b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json @@ -1476,6 +1476,17 @@ "4.0.3.0": "4.4.0" } }, + "System.Memory": { + "StableVersions": [ + "4.0.0" + ], + "BaselineVersion": "4.4.0", + "AssemblyVersionInPackageVersion": { + "4.0.0.0": "4.0.0", + "4.0.2.0": "4.3.0", + "4.0.3.0": "4.4.0" + } + }, "System.Net.Http": { "StableVersions": [ "4.0.0", @@ -2753,4 +2764,4 @@ "System.Security.Cryptography.Native.Apple": "runtime.native.System.Security.Cryptography.Apple", "System.Security.Cryptography.Native.OpenSsl": "runtime.native.System.Security.Cryptography.OpenSsl" } -} \ No newline at end of file +} diff --git a/pkg/descriptions.json b/pkg/descriptions.json index 2654b9c5c86c..68862efa6857 100644 --- a/pkg/descriptions.json +++ b/pkg/descriptions.json @@ -833,6 +833,13 @@ "System.Linq.EnumerableQuery" ] }, + { + "Name": "System.Memory", + "Description": "Provides classes for efficient low-allocation access to memory.", + "CommonTypes": [ + "System.Span", + ] + }, { "Name": "System.Net.Http", "Description": "Provides a programming interface for modern HTTP applications, including HTTP client components that allow applications to consume web services over HTTP and HTTP components that can be used by both clients and servers for parsing HTTP headers.", @@ -1623,7 +1630,7 @@ "System.ServiceProcess.ServiceType" ] }, - { + { "Name": "System.Text.Encoding", "Description": "Provides base abstract encoding classes for converting blocks of characters to and from blocks of bytes.", "CommonTypes": [ diff --git a/src/System.Memory/System.Memory.sln b/src/System.Memory/System.Memory.sln new file mode 100644 index 000000000000..9fcf61cee4c0 --- /dev/null +++ b/src/System.Memory/System.Memory.sln @@ -0,0 +1,27 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.25831.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Memory", "src\System.Memory.csproj", "{4BBC8F69-D03E-4432-AA8A-D458FA5B235A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Memory.Tests", "tests\System.Memory.Tests.csproj", "{15DC55FA-E644-4B87-A62A-DCF849031633}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4BBC8F69-D03E-4432-AA8A-D458FA5B235A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BBC8F69-D03E-4432-AA8A-D458FA5B235A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BBC8F69-D03E-4432-AA8A-D458FA5B235A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BBC8F69-D03E-4432-AA8A-D458FA5B235A}.Release|Any CPU.Build.0 = Release|Any CPU + {15DC55FA-E644-4B87-A62A-DCF849031633}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {15DC55FA-E644-4B87-A62A-DCF849031633}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15DC55FA-E644-4B87-A62A-DCF849031633}.Release|Any CPU.ActiveCfg = Release|Any CPU + {15DC55FA-E644-4B87-A62A-DCF849031633}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/System.Memory/pkg/System.Memory.builds b/src/System.Memory/pkg/System.Memory.builds new file mode 100644 index 000000000000..ad45fd50758b --- /dev/null +++ b/src/System.Memory/pkg/System.Memory.builds @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/System.Memory/pkg/System.Memory.pkgproj b/src/System.Memory/pkg/System.Memory.pkgproj new file mode 100644 index 000000000000..8200460e6481 --- /dev/null +++ b/src/System.Memory/pkg/System.Memory.pkgproj @@ -0,0 +1,10 @@ + + + + + + net45;netcore45;wp8;wpa81;netcoreapp1.0;$(AllXamarinFrameworks) + + + + diff --git a/src/System.Memory/src/Resources/Strings.resx b/src/System.Memory/src/Resources/Strings.resx new file mode 100644 index 000000000000..2b3137a71805 --- /dev/null +++ b/src/System.Memory/src/Resources/Strings.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The array type must be exactly {0}. + + + Equals() on Span and ReadOnlySpan is not supported. Use operator== instead. + + + GetHashCode() on Span and ReadOnlySpan is not supported. + + + Cannot use type '{0}'. Only value types without pointers or references are supported. + + + Cannot pass arrays to this method. Use a constructor that takes an array argument. + + + Cannot pass strings to this method. + + + Destination is too short. + + diff --git a/src/System.Memory/src/System.Memory.builds b/src/System.Memory/src/System.Memory.builds new file mode 100644 index 000000000000..2b9a9e3e3cfb --- /dev/null +++ b/src/System.Memory/src/System.Memory.builds @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/System.Memory/src/System.Memory.csproj b/src/System.Memory/src/System.Memory.csproj new file mode 100644 index 000000000000..25eb06965622 --- /dev/null +++ b/src/System.Memory/src/System.Memory.csproj @@ -0,0 +1,25 @@ + + + + + {4BBC8F69-D03E-4432-AA8A-D458FA5B235A} + System.Memory + 4.0.3.0 + true + false + .NETStandard,Version=v1.0 + $(OutputPath)$(AssemblyName).xml + + + + + + + + + + + + + + diff --git a/src/System.Memory/src/System/PinnableReferenceHelpers.Layouts.Microsoft.cs b/src/System.Memory/src/System/PinnableReferenceHelpers.Layouts.Microsoft.cs new file mode 100644 index 000000000000..456e8c37475c --- /dev/null +++ b/src/System.Memory/src/System/PinnableReferenceHelpers.Layouts.Microsoft.cs @@ -0,0 +1,35 @@ +// 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.InteropServices; +using System.Runtime.CompilerServices; + +namespace System +{ + internal static partial class PinnableReferenceHelpers + { + [StructLayout(LayoutKind.Sequential)] + private sealed class PlainObjectLayout + { + public byte Data; + } + + // Note: Does not work for Mono as Mono has an extra Bounds pointer. + [StructLayout(LayoutKind.Sequential)] + private sealed class ArrayLayout + { + private IntPtr _lengthPlusPadding; + public byte Data; + } + + [StructLayout(LayoutKind.Sequential)] + private sealed class StringLayout + { + private int _length; + public byte Data; + } + } +} + diff --git a/src/System.Memory/src/System/PinnableReferenceHelpers.cs b/src/System.Memory/src/System/PinnableReferenceHelpers.cs new file mode 100644 index 000000000000..85d9345abfd4 --- /dev/null +++ b/src/System.Memory/src/System/PinnableReferenceHelpers.cs @@ -0,0 +1,46 @@ +// 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.InteropServices; +using System.Runtime.CompilerServices; + +namespace System +{ + /// + /// Returns a pinnable reference to a standardized anchor location within various flavors of managed objects. + /// + /// Regrettably, this family of helpers is inherently unportable. It also requires special handling for arrays and strings + /// as these objects have their own header layouts. + /// + internal static partial class PinnableReferenceHelpers + { + // Returns a reference to the first field laid out in memory. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref byte GetPinnableReferenceForPlainObject(this object obj) + { + Debug.Assert(obj != null); + Debug.Assert(!(obj is Array)); + Debug.Assert(!(obj is string)); + return ref Unsafe.As(obj).Data; + } + + // Returns a reference to the 0th element (or where the 0th element would be in the case of an empty array.) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref byte GetPinnableReferenceForArray(this T[] array) + { + Debug.Assert(array != null); + return ref Unsafe.As(array).Data; + } + + // Returns a reference to the first character (or where the first character would be in the case of an empty string.) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref byte GetPinnableReferenceForString(this string s) + { + Debug.Assert(s != null); + return ref Unsafe.As(s).Data; + } + } +} + diff --git a/src/System.Memory/src/System/Span.cs b/src/System.Memory/src/System/Span.cs new file mode 100644 index 000000000000..bf81f1c6c214 --- /dev/null +++ b/src/System.Memory/src/System/Span.cs @@ -0,0 +1,424 @@ +// 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 +{ + /// + /// 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 + { + /// + /// 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) + throw new ArgumentNullException(nameof(array)); + if (default(T) == null && array.GetType() != typeof(T[])) + throw new ArrayTypeMismatchException(SR.Format(SR.ArrayTypeMustBeExactMatch, typeof(T))); + + _length = array.Length; + _object = array; + _byteOffset = IntPtr.Zero; + } + + /// + /// 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) + throw new ArgumentNullException(nameof(array)); + if (default(T) == null && array.GetType() != typeof(T[])) + throw new ArrayTypeMismatchException(SR.Format(SR.ArrayTypeMustBeExactMatch, typeof(T))); + + int arrayLength = array.Length; + if ((uint)start > (uint)arrayLength) + throw new ArgumentOutOfRangeException(nameof(start)); + + _length = arrayLength - start; + _object = array; + _byteOffset = IntPtr.Zero.Add(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) + throw new ArgumentNullException(nameof(array)); + if (default(T) == null && array.GetType() != typeof(T[])) + throw new ArrayTypeMismatchException(SR.Format(SR.ArrayTypeMustBeExactMatch, typeof(T))); + if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) + throw new ArgumentOutOfRangeException(nameof(start)); + + _length = length; + _object = array; + _byteOffset = IntPtr.Zero.Add(start); + } + + /// + /// 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. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe Span(void* pointer, int length) + { + if (!SpanHelpers.IsReferenceFree()) + throw new ArgumentException(SR.Format(SR.Argument_InvalidTypeWithPointersNotSupported, typeof(T))); + if (length < 0) + throw new ArgumentOutOfRangeException(nameof(length)); + + _length = length; + _object = null; + _byteOffset = new IntPtr(pointer); + } + + /// + /// 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 + /// "length" is not checked, nor is the fact that "rawPointer" actually lies within the object. + /// + /// The managed object that contains the data to span over. + /// A reference to data within that object. + /// The number of elements the memory contains. + /// + /// Thrown when the specified object is an array or string. Use the type-specific constructor + /// for these special cases. + /// + /// + /// Thrown when the specified object is null. + /// + /// + /// Thrown when the specified is negative. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span DangerousCreate(object obj, ref T rawPointer, int length) + { + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + if (length < 0) + throw new ArgumentOutOfRangeException(nameof(length)); + + // Disallow "special" objects that have their own object headers and would yield unexpected (and runtime-dependent) results. + // We already have constructors for arrays (and that enforce the passing of the properly typed single-dimensional array.) + // This constructor is not useful for strings (cannot express"ref s[i]"). + if (obj is Array) + throw new ArgumentException(SR.Argument_CannotPassArrayToDangerousCreate); + if (obj is string) + throw new ArgumentException(SR.Argument_CannotPassStringToDangerousCreate); + + ref T pinnableReference = ref Unsafe.As(ref obj.GetPinnableReferenceForPlainObject()); + IntPtr byteOffset = Unsafe.ByteOffset(ref pinnableReference, ref rawPointer); + return new Span(obj, byteOffset, length); + } + + // Constructor for internal use only. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span(object o, IntPtr byteOffset, int length) + { + Debug.Assert(length >= 0); + + _length = length; + _object = o; + _byteOffset = byteOffset; + } + + /// + /// 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)) + throw new IndexOutOfRangeException(); + + return ref Unsafe.Add(ref DangerousGetPinnableReference, index); + } + } + + /// + /// 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)) + throw new ArgumentException(SR.Argument_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. + /// + /// 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; + + // TODO: This is a tide-over implementation as we plan to add a overlap-safe cpblk-based api to Unsafe. (https://github.com/dotnet/corefx/issues/13427) + unsafe + { + ref T src = ref DangerousGetPinnableReference; + ref T dst = ref destination.DangerousGetPinnableReference; + IntPtr srcMinusDst = Unsafe.ByteOffset(ref dst, ref src); + int length = _length; + + bool srcGreaterThanDst = (sizeof(IntPtr) == sizeof(int)) ? srcMinusDst.ToInt32() >= 0 : srcMinusDst.ToInt64() >= 0; + if (srcGreaterThanDst) + { + // Source address greater than or equal to destination address. Can do normal copy. + for (int i = 0; i < length; i++) + { + Unsafe.Add(ref dst, i) = Unsafe.Add(ref src, i); + } + } + else + { + // Source address less than destination address. Must do backward copy. + int i = length; + while (i-- != 0) + { + Unsafe.Add(ref dst, i) = Unsafe.Add(ref src, i); + } + } + 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) + { + if (left._length != right._length) + return false; + + if (!Unsafe.AreSame(ref left.DangerousGetPinnableReference, ref right.DangerousGetPinnableReference)) + return false; + + return true; + } + + /// + /// 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.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.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); + + /// + /// 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) + throw new ArgumentOutOfRangeException(nameof(start)); + + IntPtr newOffset = _byteOffset.Add(start); + int length = _length - start; + return new Span(_object, newOffset, length); + } + + /// + /// 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)) + throw new ArgumentOutOfRangeException(nameof(start)); + + IntPtr newOffset = _byteOffset.Add(start); + return new Span(_object, newOffset, 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. + /// + public T[] ToArray() + { + if (_length == 0) + return SpanHelpers.EmptyArray(); + + T[] result = new T[_length]; + CopyTo(result); + return result; + } + + /// + /// Returns a 0-length span whose base is the null pointer. + /// + public static readonly Span Empty = default(Span); + + /// + /// 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 + { + get + { + switch (_object) + { + case null: + unsafe { return ref Unsafe.AsRef(_byteOffset.ToPointer()); } + case T[] array: + return ref Unsafe.As(ref Unsafe.Add(ref array.GetPinnableReferenceForArray(), _byteOffset)); + default: + // Supporting multidim arrays is tricky as there isn't a 100% reliable way to distinguish SzArrays from MdArray (in particular, the rank 1 multidim array with zero bounds + // which looks identical to all existing Reflection api but has a different object header than a true SzArray.) Right now, none of our constructors allow this so + // we can simply assert this here. + Debug.Assert(!(_object is Array), "Multidimensional array not expected."); + Debug.Assert(!(_object is string), "Strings only supported by ReadOnlySpan"); + return ref Unsafe.As(ref Unsafe.Add(ref _object.GetPinnableReferenceForPlainObject(), _byteOffset)); + } + } + } + + // + // The interpretations of these fields depends on how the Span was constructed: + // + // - from a native pointer + // _object == null, _byteOffset == the pointer value (think of it as the offset from NULL.) + // + // - from a T[] (call it "a") + // _object == the array, _byteOffset == offset from the address of a[0] (or in the case of an empty array, where a[0] would be.) + // + // - plain old object + // _object == the object, _byteOffset = offset from the address of the first field laid out in memory. + // + // It would be simpler if the base address could always be the start of the object header, but it is not legal to represent that address as a "ref" as the object header + // is just runtime infrastructure that no official existence in the managed worldview. + // + // As the name suggests, the offset is measured in bytes, not T's. + // + private readonly object _object; + private readonly IntPtr _byteOffset; + private readonly int _length; + } +} diff --git a/src/System.Memory/src/System/SpanHelpers.cs b/src/System.Memory/src/System/SpanHelpers.cs new file mode 100644 index 000000000000..997e45d5259f --- /dev/null +++ b/src/System.Memory/src/System/SpanHelpers.cs @@ -0,0 +1,97 @@ +// 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.Reflection; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; + +namespace System +{ + internal static class SpanHelpers + { + /// + /// Computes "start + index * sizeof(T)", using the unsigned IntPtr-sized multiplication for 32 and 64 bits. + /// + /// Assumptions: + /// Start and index are non-negative, and already pre-validated to be within the valid range of their containing Span. + /// + /// If the byte length (Span.Length * sizeof(T)) does an unsigned overflow (i.e. the buffer wraps or is too big to fit within the address space), + /// the behavior is undefined. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IntPtr Add(this IntPtr start, int index) + { + Debug.Assert(start.ToInt64() >= 0); + Debug.Assert(index >= 0); + + unsafe + { + if (sizeof(IntPtr) == sizeof(int)) + { + // 32-bit path. + uint byteLength = (uint)index * (uint)Unsafe.SizeOf(); + return (IntPtr)(((byte*)start) + byteLength); + } + else + { + // 64-bit path. + ulong byteLength = (ulong)index * (ulong)Unsafe.SizeOf(); + return (IntPtr)(((byte*)start) + byteLength); + } + } + } + + /// + /// Returns a cached empty array (Array.Empty not available on S.R. 4.0.0.0) + /// + public static T[] EmptyArray() => PerTypeLatches.EmptyArray; + + /// + /// Determine if a type is eligible for storage in unmanaged memory. TODO: To be replaced by a ContainsReference() api. + /// + public static bool IsReferenceFree() => PerTypeLatches.IsReferenceFree; + + private static bool IsReferenceFree(Type type) + { + if (type.GetTypeInfo().IsPrimitive) // This is hopefully the common case. All types that return true for this are value types w/out embedded references. + return true; + + if (!type.GetTypeInfo().IsValueType) + return false; + + // If type is a Nullable<> of something, unwrap it first. + Type underlyingNullable = Nullable.GetUnderlyingType(type); + if (underlyingNullable != null) + type = underlyingNullable; + + if (type.GetTypeInfo().IsEnum) + return true; + + foreach (FieldInfo field in type.GetTypeInfo().DeclaredFields) + { + if (field.IsStatic) + continue; + if (!IsReferenceFree(field.FieldType)) + return false; + } + return true; + } + + private static class PerTypeLatches + { + // + // Latch to ensure that excruciatingly expensive validation check for constructing a Span around a raw pointer is done + // only once per type (unless of course, the validation fails.) + // + // false == not yet computed or found to be not reference free. + // true == confirmed reference free + // + public static readonly bool IsReferenceFree = IsReferenceFree(typeof(T)); + + public static readonly T[] EmptyArray = new T[0]; + } + } +} diff --git a/src/System.Memory/src/project.json b/src/System.Memory/src/project.json new file mode 100644 index 000000000000..3491f356642a --- /dev/null +++ b/src/System.Memory/src/project.json @@ -0,0 +1,13 @@ +{ + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Diagnostics.Debug": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Reflection": "4.1.0", + "System.Runtime.CompilerServices.Unsafe": "4.4.0-beta-24710-01" + }, + "frameworks": { + "netstandard1.0": {} + } +} diff --git a/src/System.Memory/tests/Span/CopyTo.cs b/src/System.Memory/tests/Span/CopyTo.cs new file mode 100644 index 000000000000..9993493a6130 --- /dev/null +++ b/src/System.Memory/tests/Span/CopyTo.cs @@ -0,0 +1,87 @@ +// 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 Xunit; + +namespace System.SpanTests +{ + public static partial class SpanTests + { + [Fact] + public static void TryCopyTo() + { + int[] src = { 1, 2, 3 }; + int[] dst = { 99, 100, 101 }; + + Span srcSpan = new Span(src); + bool success = srcSpan.TryCopyTo(dst); + Assert.True(success); + Assert.Equal(src, dst); + } + + [Fact] + public static void TryCopyToLonger() + { + int[] src = { 1, 2, 3 }; + int[] dst = { 99, 100, 101, 102 }; + + Span srcSpan = new Span(src); + bool success = srcSpan.TryCopyTo(dst); + Assert.True(success); + int[] expected = { 1, 2, 3, 102 }; + Assert.Equal(expected, dst); + } + + [Fact] + public static void TryCopyToShorter() + { + int[] src = { 1, 2, 3 }; + int[] dst = { 99, 100 }; + + Span srcSpan = new Span(src); + bool success = srcSpan.TryCopyTo(dst); + Assert.False(success); + int[] expected = { 99, 100 }; + Assert.Equal(expected, dst); // TryCopyTo() checks for sufficient space before doing any copying. + } + + [Fact] + public static void CopyToShorter() + { + int[] src = { 1, 2, 3 }; + int[] dst = { 99, 100 }; + + Span srcSpan = new Span(src); + Assert.Throws(() => srcSpan.CopyTo(dst)); + int[] expected = { 99, 100 }; + Assert.Equal(expected, dst); // CopyTo() checks for sufficient space before doing any copying. + } + + [Fact] + public static void Overlapping1() + { + int[] a = { 90, 91, 92, 93, 94, 95, 96, 97 }; + + Span src = new Span(a, 1, 6); + Span dst = new Span(a, 2, 6); + src.CopyTo(dst); + + int[] expected = { 90, 91, 91, 92, 93, 94, 95, 96 }; + Assert.Equal(expected, a); + } + + [Fact] + public static void Overlapping2() + { + int[] a = { 90, 91, 92, 93, 94, 95, 96, 97 }; + + Span src = new Span(a, 2, 6); + Span dst = new Span(a, 1, 6); + src.CopyTo(dst); + + int[] expected = { 90, 92, 93, 94, 95, 96, 97, 97 }; + Assert.Equal(expected, a); + } + } +} diff --git a/src/System.Memory/tests/Span/CtorArray.cs b/src/System.Memory/tests/Span/CtorArray.cs new file mode 100644 index 000000000000..0953e4a6a5e7 --- /dev/null +++ b/src/System.Memory/tests/Span/CtorArray.cs @@ -0,0 +1,121 @@ +// 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 Xunit; + +namespace System.SpanTests +{ + // + // Tests for Span.ctor(T[]) + // + // These tests will also exercise the matching codepaths in Span.ctor(T[], int) and .ctor(T[], int, int). This makes it easier to ensure + // that these parallel tests stay consistent, and avoid excess repetition in the files devoted to those specific overloads. + // + public static partial class SpanTests + { + [Fact] + public static void CtorArray1() + { + int[] a = { 91, 92, -93, 94 }; + Span span; + + span = new Span(a); + span.Validate(91, 92, -93, 94); + + span = new Span(a, 0); + span.Validate(91, 92, -93, 94); + + span = new Span(a, 0, a.Length); + span.Validate(91, 92, -93, 94); + } + + [Fact] + public static void CtorArray2() + { + long[] a = { 91, -92, 93, 94, -95 }; + Span span; + + span = new Span(a); + span.Validate(91, -92, 93, 94, -95); + + span = new Span(a, 0); + span.Validate(91, -92, 93, 94, -95); + + span = new Span(a, 0, a.Length); + span.Validate(91, -92, 93, 94, -95); + } + + [Fact] + public static void CtorArray3() + { + object o1 = new object(); + object o2 = new object(); + object[] a = { o1, o2 }; + Span span; + + span = new Span(a); + span.Validate(o1, o2); + + span = new Span(a, 0); + span.Validate(o1, o2); + + span = new Span(a, 0, a.Length); + span.Validate(o1, o2); + } + + [Fact] + public static void CtorArrayZeroLength() + { + int[] empty = Array.Empty(); + Span span; + + span = new Span(empty); + span.Validate(); + + span = new Span(empty, 0); + span.Validate(); + + span = new Span(empty, 0, empty.Length); + span.Validate(); + } + + [Fact] + public static void CtorArrayNullArray() + { + Assert.Throws(() => new Span((int[])null)); + Assert.Throws(() => new Span((int[])null, 0)); + Assert.Throws(() => new Span((int[])null, 0, 0)); + } + + [Fact] + public static void CtorArrayWrongArrayType() + { + // Cannot pass variant array, if array type is not a valuetype. + string[] a = { "Hello" }; + Assert.Throws(() => new Span(a)); + Assert.Throws(() => new Span(a, 0)); + Assert.Throws(() => new Span(a, 0, a.Length)); + } + + [Fact] + public static void CtorArrayWrongValueType() + { + // Can pass variant array, if array type is a valuetype. + + uint[] a = { 42u, 0xffffffffu }; + int[] aAsIntArray = (int[])(object)a; + Span span; + + span = new Span(aAsIntArray); + span.Validate(42, -1); + + span = new Span(aAsIntArray, 0); + span.Validate(42, -1); + + span = new Span(aAsIntArray, 0, aAsIntArray.Length); + span.Validate(42, -1); + } + } +} + diff --git a/src/System.Memory/tests/Span/CtorArrayInt.cs b/src/System.Memory/tests/Span/CtorArrayInt.cs new file mode 100644 index 000000000000..ec229bf18074 --- /dev/null +++ b/src/System.Memory/tests/Span/CtorArrayInt.cs @@ -0,0 +1,54 @@ +// 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 Xunit; + +namespace System.SpanTests +{ + // + // Tests for Span.ctor(T[], int). If the test is not specific to this overload, consider putting it in CtorArray.cs instread. + // + public static partial class SpanTests + { + [Fact] + public static void CtorArrayInt1() + { + int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98 }; + Span span = new Span(a, 3); + span.Validate(93, 94, 95, 96, 97, 98); + } + + [Fact] + public static void CtorArrayInt2() + { + long[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98 }; + Span span = new Span(a, 3); + span.Validate(93, 94, 95, 96, 97, 98); + } + + [Fact] + public static void CtorArrayIntNegativeStart() + { + int[] a = new int[3]; + Assert.Throws(() => new Span(a, -1)); + } + + [Fact] + public static void CtorArrayIntStartTooLarge() + { + int[] a = new int[3]; + Assert.Throws(() => new Span(a, 4)); + } + + [Fact] + public static void CtorArrayIntStartEqualsLength() + { + // Valid for start to equal the array length. This returns an empty span that starts "just past the array." + int[] a = { 91, 92, 93 }; + Span span = new Span(a, 3); + span.Validate(); + } + } +} + diff --git a/src/System.Memory/tests/Span/CtorArrayIntInt.cs b/src/System.Memory/tests/Span/CtorArrayIntInt.cs new file mode 100644 index 000000000000..4a9c27184c95 --- /dev/null +++ b/src/System.Memory/tests/Span/CtorArrayIntInt.cs @@ -0,0 +1,80 @@ +// 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 Xunit; + +namespace System.SpanTests +{ + // + // Tests for Span.ctor(T[], int, int). If the test is not specific to this overload, consider putting it in CtorArray.cs instread. + // + public static partial class SpanTests + { + [Fact] + public static void CtorArrayIntInt1() + { + int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98 }; + Span span = new Span(a, 3, 2); + span.Validate(93, 94); + } + + [Fact] + public static void CtorArrayIntInt2() + { + long[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98 }; + Span span = new Span(a, 4, 3); + span.Validate(94, 95, 96); + } + + [Fact] + public static void CtorArrayIntIntRangeExtendsToEndOfArray() + { + long[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98 }; + Span span = new Span(a, 4, 5); + span.Validate(94, 95, 96, 97, 98); + } + + [Fact] + public static void CtorArrayIntIntNegativeStart() + { + int[] a = new int[3]; + Assert.Throws(() => new Span(a, -1, 0)); + } + + [Fact] + public static void CtorArrayIntIntStartTooLarge() + { + int[] a = new int[3]; + Assert.Throws(() => new Span(a, 4, 0)); + } + + [Fact] + public static void CtorArrayIntIntNegativeLength() + { + int[] a = new int[3]; + Assert.Throws(() => new Span(a, 0, -1)); + } + + [Fact] + public static void CtorArrayIntIntStartAndLengthTooLarge() + { + int[] a = new int[3]; + Assert.Throws(() => new Span(a, 3, 1)); + Assert.Throws(() => new Span(a, 2, 2)); + Assert.Throws(() => new Span(a, 1, 3)); + Assert.Throws(() => new Span(a, 0, 4)); + Assert.Throws(() => new Span(a, int.MaxValue, int.MaxValue)); + } + + [Fact] + public static void CtorArrayIntIntStartEqualsLength() + { + // Valid for start to equal the array length. This returns an empty span that starts "just past the array." + int[] a = { 91, 92, 93 }; + Span span = new Span(a, 3, 0); + span.Validate(); + } + } +} + diff --git a/src/System.Memory/tests/Span/CtorPointerInt.cs b/src/System.Memory/tests/Span/CtorPointerInt.cs new file mode 100644 index 000000000000..9b4671897162 --- /dev/null +++ b/src/System.Memory/tests/Span/CtorPointerInt.cs @@ -0,0 +1,78 @@ +// 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 Xunit; +using System.Runtime.CompilerServices; + +#pragma warning disable 0649 //Field 'SpanTests.InnerStruct.J' is never assigned to, and will always have its default value 0 + +namespace System.SpanTests +{ + public static partial class SpanTests + { + [Fact] + public static void CtorPointerInt() + { + unsafe + { + int[] a = { 90, 91, 92 }; + fixed (int *pa = a) + { + Span span = new Span(pa, 3); + span.Validate(90, 91, 92); + Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(pa), ref span[0])); + } + } + } + + [Fact] + public static void CtorPointerNull() + { + unsafe + { + Span span = new Span((void*)null, 0); + span.Validate(); + Assert.True(Unsafe.AreSame(ref Unsafe.AsRef((void*)null), ref span.DangerousGetPinnableReference)); + } + } + + [Fact] + public static void CtorPointerRangeChecks() + { + unsafe + { + Assert.Throws( + delegate () + { + int i = 42; + Span span = new Span(&i, -1); + }); + } + } + + [Fact] + public static void CtorPointerNoContainsReferenceEnforcement() + { + unsafe + { + new Span((void*)null, 0); + new Span((void*)null, 0); + Assert.Throws(() => new Span((void*)null, 0)); + Assert.Throws(() => new Span((void*)null, 0)); + } + } + + private struct StructWithReferences + { + public int I; + public InnerStruct Inner; + } + + private struct InnerStruct + { + public int J; + public object O; + } + } +} diff --git a/src/System.Memory/tests/Span/DangerousCreate.cs b/src/System.Memory/tests/Span/DangerousCreate.cs new file mode 100644 index 000000000000..04dcf7705f77 --- /dev/null +++ b/src/System.Memory/tests/Span/DangerousCreate.cs @@ -0,0 +1,74 @@ +// 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 Xunit; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; + +namespace System.SpanTests +{ + public static partial class SpanTests + { + [Fact] + public static void DangerousCreateNullObject() + { + Assert.Throws( + delegate () + { + int dummy = 4; + Span.DangerousCreate(null, ref dummy, 0); + }); + } + + [Fact] + public static void DangerousCreateNoArrays() + { + Assert.Throws( + delegate () + { + int[] a = new int[4]; + Span.DangerousCreate(a, ref a[0], 0); + }); + } + + [Fact] + public static void DangerousCreateNoStrings() + { + Assert.Throws( + delegate () + { + char dummy = 'A'; + Span.DangerousCreate("Hello", ref dummy, 0); + }); + } + + + [Fact] + public static void DangerousCreateBadLength() + { + Assert.Throws( + delegate () + { + TestClass testClass = new TestClass(); + Span span = Span.DangerousCreate(testClass, ref testClass.C1, -1); + }); + } + + [Fact] + public static void DangerousCreate1() + { + TestClass testClass = new TestClass(); + testClass.C0 = 'a'; + testClass.C1 = 'b'; + testClass.C2 = 'c'; + testClass.C3 = 'd'; + testClass.C4 = 'e'; + Span span = Span.DangerousCreate(testClass, ref testClass.C1, 3); + span.Validate('b', 'c', 'd'); + + ref char pc1 = ref span[0]; + Assert.True(Unsafe.AreSame(ref testClass.C1, ref pc1)); + } + } +} diff --git a/src/System.Memory/tests/Span/DangerousGetPinnableReference.cs b/src/System.Memory/tests/Span/DangerousGetPinnableReference.cs new file mode 100644 index 000000000000..40119f7830fe --- /dev/null +++ b/src/System.Memory/tests/Span/DangerousGetPinnableReference.cs @@ -0,0 +1,68 @@ +// 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 Xunit; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; + +namespace System.SpanTests +{ + public static partial class SpanTests + { + [Fact] + public static void DangerousGetPinnableReferenceArray() + { + int[] a = { 91, 92, 93, 94, 95 }; + Span span = new Span(a, 1, 3); + ref int pinnableReference = ref span.DangerousGetPinnableReference; + Assert.True(Unsafe.AreSame(ref a[1], ref pinnableReference)); + } + + [Fact] + public static void DangerousGetPinnableReferenceArrayPastEnd() + { + // The only real difference between DangerousGetPinnableReference and "ref span[0]" is that + // DangerousGetPinnableReference of a zero-length won't throw an IndexOutOfRange. + + int[] a = { 91, 92, 93, 94, 95 }; + Span span = new Span(a, a.Length, 0); + ref int pinnableReference = ref span.DangerousGetPinnableReference; + ref int expected = ref Unsafe.Add(ref a[a.Length - 1], 1); + Assert.True(Unsafe.AreSame(ref expected, ref pinnableReference)); + } + + [Fact] + public static void DangerousGetPinnableReferencePointer() + { + unsafe + { + int i = 42; + Span span = new Span(&i, 1); + ref int pinnableReference = ref span.DangerousGetPinnableReference; + Assert.True(Unsafe.AreSame(ref i, ref pinnableReference)); + } + } + + [Fact] + public static void DangerousGetPinnableReferencePointerDangerousCreate1() + { + TestClass testClass = new TestClass(); + Span span = Span.DangerousCreate(testClass, ref testClass.C1, 3); + + ref char pinnableReference = ref span.DangerousGetPinnableReference; + Assert.True(Unsafe.AreSame(ref testClass.C1, ref pinnableReference)); + } + + [Fact] + public static void DangerousGetPinnableReferenceEmpty() + { + unsafe + { + Span span = Span.Empty; + ref int pinnableReference = ref span.DangerousGetPinnableReference; + Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(null), ref pinnableReference)); + } + } + } +} diff --git a/src/System.Memory/tests/Span/Empty.cs b/src/System.Memory/tests/Span/Empty.cs new file mode 100644 index 000000000000..9d6ed11e9f55 --- /dev/null +++ b/src/System.Memory/tests/Span/Empty.cs @@ -0,0 +1,26 @@ +// 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 Xunit; +using System.Runtime.CompilerServices; + +namespace System.SpanTests +{ + public static partial class SpanTests + { + [Fact] + public static void Empty() + { + Span empty = Span.Empty; + Assert.True(empty.IsEmpty); + Assert.Equal(0, empty.Length); + unsafe + { + ref int expected = ref Unsafe.AsRef(null); + ref int actual = ref empty.DangerousGetPinnableReference; + Assert.True(Unsafe.AreSame(ref expected, ref actual)); + } + } + } +} diff --git a/src/System.Memory/tests/Span/Equality.cs b/src/System.Memory/tests/Span/Equality.cs new file mode 100644 index 000000000000..688682446af1 --- /dev/null +++ b/src/System.Memory/tests/Span/Equality.cs @@ -0,0 +1,93 @@ +// 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 Xunit; + +#pragma warning disable 1718 //Comparison made to same variable; did you mean to compare something else? + +namespace System.SpanTests +{ + public static partial class SpanTests + { + [Fact] + public static void EqualityTrue() + { + int[] a = { 91, 92, 93, 94, 95 }; + Span left = new Span(a, 2, 3); + Span right = new Span(a, 2, 3); + + Assert.True(left == right); + Assert.True(!(left != right)); + } + + [Fact] + public static void EqualityReflexivity() + { + int[] a = { 91, 92, 93, 94, 95 }; + Span left = new Span(a, 2, 3); + + Assert.True(left == left); + Assert.True(!(left != left)); + } + + [Fact] + public static void EqualityIncludesLength() + { + int[] a = { 91, 92, 93, 94, 95 }; + Span left = new Span(a, 2, 1); + Span right = new Span(a, 2, 3); + + Assert.False(left == right); + Assert.False(!(left != right)); + } + + [Fact] + public static void EqualityIncludesBase() + { + int[] a = { 91, 92, 93, 94, 95 }; + Span left = new Span(a, 1, 3); + Span right = new Span(a, 2, 3); + + Assert.False(left == right); + Assert.False(!(left != right)); + } + + [Fact] + public static void EqualityBasedOnLocationNotConstructor() + { + unsafe + { + int[] a = { 91, 92, 93, 94, 95 }; + fixed (int* pa = a) + { + Span left = new Span(a, 2, 3); + Span right = new Span(pa + 2, 3); + + Assert.True(left == right); + Assert.True(!(left != right)); + } + } + } + + [Fact] + public static void EqualityComparesRangeNotContent() + { + Span left = new Span(new int[] { 0, 1, 2 }, 1, 1); + Span right = new Span(new int[] { 0, 1, 2 }, 1, 1); + + Assert.False(left == right); + Assert.False(!(left != right)); + } + + [Fact] + public static void EmptySpansNotUnified() + { + Span left = new Span(new int[0]); + Span right = new Span(new int[0]); + + Assert.False(left == right); + Assert.False(!(left != right)); + } + } +} diff --git a/src/System.Memory/tests/Span/Overflow.cs b/src/System.Memory/tests/Span/Overflow.cs new file mode 100644 index 000000000000..771a315607cf --- /dev/null +++ b/src/System.Memory/tests/Span/Overflow.cs @@ -0,0 +1,64 @@ +// 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 Xunit; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; + +namespace System.SpanTests +{ + public static partial class SpanTests + { + [Fact] + public static void IndexOverflow() + { + // + // Although Span constrains indexes to 0..2Gb, it does not similarly constrain index * sizeof(T). + // Make sure that internal offset calculcations handle the >2Gb case properly. + // + unsafe + { + byte* pMemory; + try + { + pMemory = (byte*)Marshal.AllocHGlobal((IntPtr)ThreeGiB); + } + catch (Exception) + { + return; // It's not implausible to believe that a 3gb allocation will fail - if so, skip this test to avoid unnecessary test flakiness. + } + + try + { + Span span = new Span(pMemory, GuidThreeGiBLimit); + + int bigIndex = checked(GuidTwoGiBLimit + 1); + uint byteOffset = checked((uint)bigIndex * (uint)sizeof(Guid)); + Assert.True(byteOffset > (uint)int.MaxValue); // Make sure byteOffset actually overflows 2Gb, or this test is pointless. + ref Guid expected = ref Unsafe.AsRef(((byte*)pMemory) + byteOffset); + + Assert.True(Unsafe.AreSame(ref expected, ref span[bigIndex])); + + Span slice = span.Slice(bigIndex); + Assert.True(Unsafe.AreSame(ref expected, ref slice[0])); + + slice = span.Slice(bigIndex, 1); + Assert.True(Unsafe.AreSame(ref expected, ref slice[0])); + } + finally + { + Marshal.FreeHGlobal((IntPtr)pMemory); + } + } + } + + private const long ThreeGiB = 3L * 1024L * 1024L * 1024L; + private const long TwoGiB = 2L * 1024L * 1024L * 1024L; + private const long OneGiB = 1L * 1024L * 1024L * 1024L; + + private static readonly int GuidThreeGiBLimit = (int)(ThreeGiB / Unsafe.SizeOf()); // sizeof(Guid) requires unsafe keyword and I don't want to mark the entire class unsafe. + private static readonly int GuidTwoGiBLimit = (int)(TwoGiB / Unsafe.SizeOf()); + private static readonly int GuidOneGiBLimit = (int)(OneGiB / Unsafe.SizeOf()); + } +} diff --git a/src/System.Memory/tests/Span/Slice.cs b/src/System.Memory/tests/Span/Slice.cs new file mode 100644 index 000000000000..fd45199f2917 --- /dev/null +++ b/src/System.Memory/tests/Span/Slice.cs @@ -0,0 +1,70 @@ +// 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 Xunit; +using System.Runtime.CompilerServices; + +namespace System.SpanTests +{ + public static partial class SpanTests + { + [Fact] + public static void SliceInt() + { + int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }; + Span span = new Span(a).Slice(6); + Assert.Equal(4, span.Length); + Assert.True(Unsafe.AreSame(ref a[6], ref span[0])); + } + + [Fact] + public static void SliceIntPastEnd() + { + int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }; + Span span = new Span(a).Slice(a.Length); + Assert.Equal(0, span.Length); + Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref span.DangerousGetPinnableReference, 1))); + } + + [Fact] + public static void SliceIntInt() + { + int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }; + Span span = new Span(a).Slice(3, 5); + Assert.Equal(5, span.Length); + Assert.True(Unsafe.AreSame(ref a[3], ref span[0])); + } + + [Fact] + public static void SliceIntIntUpToEnd() + { + int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }; + Span span = new Span(a).Slice(4, 6); + Assert.Equal(6, span.Length); + Assert.True(Unsafe.AreSame(ref a[4], ref span[0])); + } + + [Fact] + public static void SliceIntIntPastEnd() + { + int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }; + Span span = new Span(a).Slice(a.Length, 0); + Assert.Equal(0, span.Length); + Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref span.DangerousGetPinnableReference, 1))); + } + + [Fact] + public static void SliceIntRangeChecksd() + { + int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }; + Assert.Throws(() => new Span(a).Slice(-1)); + Assert.Throws(() => new Span(a).Slice(a.Length + 1)); + Assert.Throws(() => new Span(a).Slice(-1, 0)); + Assert.Throws(() => new Span(a).Slice(0, a.Length + 1)); + Assert.Throws(() => new Span(a).Slice(2, a.Length + 1 - 2)); + Assert.Throws(() => new Span(a).Slice(a.Length + 1, 0)); + Assert.Throws(() => new Span(a).Slice(a.Length, 1)); + } + } +} diff --git a/src/System.Memory/tests/Span/TestHelpers.cs b/src/System.Memory/tests/Span/TestHelpers.cs new file mode 100644 index 000000000000..b90227aa9fe5 --- /dev/null +++ b/src/System.Memory/tests/Span/TestHelpers.cs @@ -0,0 +1,60 @@ +// 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 Xunit; +using System.Runtime.InteropServices; + +namespace System.SpanTests +{ + public static partial class SpanTests + { + private static void Validate(this Span span, params T[] expected) + { + bool isValueType = default(T) != null || Nullable.GetUnderlyingType(typeof(T)) != null; + Assert.Equal(span.Length, expected.Length); + for (int i = 0; i < expected.Length; i++) + { + T actual = span[i]; + if (isValueType) + Assert.Equal(expected[i], actual); + else + Assert.Same(expected[i], actual); + } + + object ignore; + AssertThrows(span, (_span) => ignore = _span[expected.Length]); + } + + private delegate void AssertThrowsAction(Span span); + + // Cannot use standard Assert.Throws() when testing Span - Span and closures don't get along. + private static void AssertThrows(Span span, AssertThrowsAction action) where E:Exception + { + try + { + action(span); + Assert.False(true, "Expected exception: " + typeof(E).GetType()); + } + catch (E) + { + } + catch (Exception wrongException) + { + Assert.False(true, "Wrong exception thrown: Expected " + typeof(E).GetType() + ": Actual: " + wrongException.GetType()); + } + } + + [StructLayout(LayoutKind.Sequential)] + private sealed class TestClass + { + private double _d; + public char C0; + public char C1; + public char C2; + public char C3; + public char C4; + } + } +} + diff --git a/src/System.Memory/tests/Span/ToArray.cs b/src/System.Memory/tests/Span/ToArray.cs new file mode 100644 index 000000000000..ecabef068406 --- /dev/null +++ b/src/System.Memory/tests/Span/ToArray.cs @@ -0,0 +1,30 @@ +// 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 Xunit; +using System.Runtime.CompilerServices; + +namespace System.SpanTests +{ + public static partial class SpanTests + { + [Fact] + public static void ToArray1() + { + int[] a = { 91, 92, 93 }; + Span span = new Span(a); + int[] copy = span.ToArray(); + Assert.Equal(a, copy); + Assert.NotSame(a, copy); + } + + [Fact] + public static void ToArrayEmpty() + { + Span span = Span.Empty; + int[] copy = span.ToArray(); + Assert.Equal(0, copy.Length); + } + } +} diff --git a/src/System.Memory/tests/System.Memory.Tests.builds b/src/System.Memory/tests/System.Memory.Tests.builds new file mode 100644 index 000000000000..75b5c3f9cbb0 --- /dev/null +++ b/src/System.Memory/tests/System.Memory.Tests.builds @@ -0,0 +1,16 @@ + + + + + + Windows_NT + netcore50;netcoreapp1.0;net46 + + + netcoreapp1.1 + Windows_NT + netcoreapp1.1 + + + + diff --git a/src/System.Memory/tests/System.Memory.Tests.csproj b/src/System.Memory/tests/System.Memory.Tests.csproj new file mode 100644 index 000000000000..aae27bdcc583 --- /dev/null +++ b/src/System.Memory/tests/System.Memory.Tests.csproj @@ -0,0 +1,32 @@ + + + + + System.Memory.Tests + true + + + + + + + + + + + + + + + + + + + + + Memory + + + + + From d015dcf25d6ba0800671ee43ede253f8136ac5f1 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 07:13:01 -0800 Subject: [PATCH 02/26] Remove the type casts from DangerousGetPinnedReference - store object as Pinnable --- src/System.Memory/src/System.Memory.csproj | 4 +- src/System.Memory/src/System/ArrayHeader.cs | 20 +++++ src/System.Memory/src/System/Pinnable.cs | 18 +++++ ...nableReferenceHelpers.Layouts.Microsoft.cs | 35 --------- .../src/System/PinnableReferenceHelpers.cs | 46 ------------ src/System.Memory/src/System/Span.cs | 73 ++++++++----------- 6 files changed, 71 insertions(+), 125 deletions(-) create mode 100644 src/System.Memory/src/System/ArrayHeader.cs create mode 100644 src/System.Memory/src/System/Pinnable.cs delete mode 100644 src/System.Memory/src/System/PinnableReferenceHelpers.Layouts.Microsoft.cs delete mode 100644 src/System.Memory/src/System/PinnableReferenceHelpers.cs diff --git a/src/System.Memory/src/System.Memory.csproj b/src/System.Memory/src/System.Memory.csproj index 25eb06965622..744d95c3f955 100644 --- a/src/System.Memory/src/System.Memory.csproj +++ b/src/System.Memory/src/System.Memory.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/src/System.Memory/src/System/ArrayHeader.cs b/src/System.Memory/src/System/ArrayHeader.cs new file mode 100644 index 000000000000..de62e808c9a3 --- /dev/null +++ b/src/System.Memory/src/System/ArrayHeader.cs @@ -0,0 +1,20 @@ +// 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.InteropServices; + +namespace System +{ + // + // This is a "magic constant" that defines how much offset to add to Pinnable.Data to get to the first element of an array. + // This is necessarily non-portable, but it works for the CLR and CoreClr. It does not work for Mono (Mono has an extra Bounds + // pointer) but Mono wants to do Span intrinsically. + // + [StructLayout(LayoutKind.Sequential)] + internal struct ArrayHeader + { + private IntPtr _lengthPlusPadding; + } +} diff --git a/src/System.Memory/src/System/Pinnable.cs b/src/System.Memory/src/System/Pinnable.cs new file mode 100644 index 000000000000..0f9b02ad94c6 --- /dev/null +++ b/src/System.Memory/src/System/Pinnable.cs @@ -0,0 +1,18 @@ +// 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.InteropServices; + +namespace System +{ + // + // This class exists solely so that arbitrary objects can be Unsafe-casted to it to get a ref to the start of the user data. + // + [StructLayout(LayoutKind.Sequential)] + internal sealed class Pinnable + { + public T Data; + } +} diff --git a/src/System.Memory/src/System/PinnableReferenceHelpers.Layouts.Microsoft.cs b/src/System.Memory/src/System/PinnableReferenceHelpers.Layouts.Microsoft.cs deleted file mode 100644 index 456e8c37475c..000000000000 --- a/src/System.Memory/src/System/PinnableReferenceHelpers.Layouts.Microsoft.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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.InteropServices; -using System.Runtime.CompilerServices; - -namespace System -{ - internal static partial class PinnableReferenceHelpers - { - [StructLayout(LayoutKind.Sequential)] - private sealed class PlainObjectLayout - { - public byte Data; - } - - // Note: Does not work for Mono as Mono has an extra Bounds pointer. - [StructLayout(LayoutKind.Sequential)] - private sealed class ArrayLayout - { - private IntPtr _lengthPlusPadding; - public byte Data; - } - - [StructLayout(LayoutKind.Sequential)] - private sealed class StringLayout - { - private int _length; - public byte Data; - } - } -} - diff --git a/src/System.Memory/src/System/PinnableReferenceHelpers.cs b/src/System.Memory/src/System/PinnableReferenceHelpers.cs deleted file mode 100644 index 85d9345abfd4..000000000000 --- a/src/System.Memory/src/System/PinnableReferenceHelpers.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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.InteropServices; -using System.Runtime.CompilerServices; - -namespace System -{ - /// - /// Returns a pinnable reference to a standardized anchor location within various flavors of managed objects. - /// - /// Regrettably, this family of helpers is inherently unportable. It also requires special handling for arrays and strings - /// as these objects have their own header layouts. - /// - internal static partial class PinnableReferenceHelpers - { - // Returns a reference to the first field laid out in memory. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref byte GetPinnableReferenceForPlainObject(this object obj) - { - Debug.Assert(obj != null); - Debug.Assert(!(obj is Array)); - Debug.Assert(!(obj is string)); - return ref Unsafe.As(obj).Data; - } - - // Returns a reference to the 0th element (or where the 0th element would be in the case of an empty array.) - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref byte GetPinnableReferenceForArray(this T[] array) - { - Debug.Assert(array != null); - return ref Unsafe.As(array).Data; - } - - // Returns a reference to the first character (or where the first character would be in the case of an empty string.) - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref byte GetPinnableReferenceForString(this string s) - { - Debug.Assert(s != null); - return ref Unsafe.As(s).Data; - } - } -} - diff --git a/src/System.Memory/src/System/Span.cs b/src/System.Memory/src/System/Span.cs index bf81f1c6c214..4915b4172cb6 100644 --- a/src/System.Memory/src/System/Span.cs +++ b/src/System.Memory/src/System/Span.cs @@ -33,8 +33,8 @@ public Span(T[] array) throw new ArrayTypeMismatchException(SR.Format(SR.ArrayTypeMustBeExactMatch, typeof(T))); _length = array.Length; - _object = array; - _byteOffset = IntPtr.Zero; + _pinnable = Unsafe.As>(array); + unsafe { _byteOffset = (IntPtr)sizeof(ArrayHeader); } } /// @@ -62,8 +62,8 @@ public Span(T[] array, int start) throw new ArgumentOutOfRangeException(nameof(start)); _length = arrayLength - start; - _object = array; - _byteOffset = IntPtr.Zero.Add(start); + _pinnable = Unsafe.As>(array); + unsafe { _byteOffset = ((IntPtr)sizeof(ArrayHeader)).Add(start); } } /// @@ -90,8 +90,8 @@ public Span(T[] array, int start, int length) throw new ArgumentOutOfRangeException(nameof(start)); _length = length; - _object = array; - _byteOffset = IntPtr.Zero.Add(start); + _pinnable = Unsafe.As>(array); + unsafe { _byteOffset = ((IntPtr)sizeof(ArrayHeader)).Add(start); } } /// @@ -117,7 +117,7 @@ public unsafe Span(void* pointer, int length) throw new ArgumentOutOfRangeException(nameof(length)); _length = length; - _object = null; + _pinnable = null; _byteOffset = new IntPtr(pointer); } @@ -155,19 +155,19 @@ public static Span DangerousCreate(object obj, ref T rawPointer, int length) if (obj is string) throw new ArgumentException(SR.Argument_CannotPassStringToDangerousCreate); - ref T pinnableReference = ref Unsafe.As(ref obj.GetPinnableReferenceForPlainObject()); - IntPtr byteOffset = Unsafe.ByteOffset(ref pinnableReference, ref rawPointer); - return new Span(obj, byteOffset, length); + Pinnable pinnable = Unsafe.As>(obj); + IntPtr byteOffset = Unsafe.ByteOffset(ref pinnable.Data, ref rawPointer); + return new Span(pinnable, byteOffset, length); } // Constructor for internal use only. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Span(object o, IntPtr byteOffset, int length) + private Span(Pinnable pinnable, IntPtr byteOffset, int length) { Debug.Assert(length >= 0); _length = length; - _object = o; + _pinnable = pinnable; _byteOffset = byteOffset; } @@ -197,7 +197,11 @@ public ref T this[int index] if ((uint)index >= ((uint)_length)) throw new IndexOutOfRangeException(); - return ref Unsafe.Add(ref DangerousGetPinnableReference, index); + IntPtr adjustedByteOffset = _byteOffset.Add(index); + if (_pinnable == null) + unsafe { return ref Unsafe.AsRef(adjustedByteOffset.ToPointer()); } + else + return ref Unsafe.AddByteOffset(ref _pinnable.Data, adjustedByteOffset); } } @@ -334,7 +338,7 @@ public Span Slice(int start) IntPtr newOffset = _byteOffset.Add(start); int length = _length - start; - return new Span(_object, newOffset, length); + return new Span(_pinnable, newOffset, length); } /// @@ -352,7 +356,7 @@ public Span Slice(int start, int length) throw new ArgumentOutOfRangeException(nameof(start)); IntPtr newOffset = _byteOffset.Add(start); - return new Span(_object, newOffset, length); + return new Span(_pinnable, newOffset, length); } /// @@ -381,43 +385,28 @@ public T[] ToArray() /// public ref T DangerousGetPinnableReference { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - switch (_object) - { - case null: - unsafe { return ref Unsafe.AsRef(_byteOffset.ToPointer()); } - case T[] array: - return ref Unsafe.As(ref Unsafe.Add(ref array.GetPinnableReferenceForArray(), _byteOffset)); - default: - // Supporting multidim arrays is tricky as there isn't a 100% reliable way to distinguish SzArrays from MdArray (in particular, the rank 1 multidim array with zero bounds - // which looks identical to all existing Reflection api but has a different object header than a true SzArray.) Right now, none of our constructors allow this so - // we can simply assert this here. - Debug.Assert(!(_object is Array), "Multidimensional array not expected."); - Debug.Assert(!(_object is string), "Strings only supported by ReadOnlySpan"); - return ref Unsafe.As(ref Unsafe.Add(ref _object.GetPinnableReferenceForPlainObject(), _byteOffset)); - } + if (_pinnable == null) + unsafe { return ref Unsafe.AsRef(_byteOffset.ToPointer()); } + else + return ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset); } } // - // The interpretations of these fields depends on how the Span was constructed: - // - // - from a native pointer - // _object == null, _byteOffset == the pointer value (think of it as the offset from NULL.) + // If the Span was constructed from an object, // - // - from a T[] (call it "a") - // _object == the array, _byteOffset == offset from the address of a[0] (or in the case of an empty array, where a[0] would be.) - // - // - plain old object - // _object == the object, _byteOffset = offset from the address of the first field laid out in memory. + // _pinnable = that object (unsafe-casted to a Pinnable) + // _byteOffset = offset in bytes from "ref _pinnable.Data" to "ref span[0]" // - // It would be simpler if the base address could always be the start of the object header, but it is not legal to represent that address as a "ref" as the object header - // is just runtime infrastructure that no official existence in the managed worldview. + // If the Span was constructed from a native pointer, // - // As the name suggests, the offset is measured in bytes, not T's. + // _pinnable = null + // _byteOffset = the pointer // - private readonly object _object; + private readonly Pinnable _pinnable; private readonly IntPtr _byteOffset; private readonly int _length; } From 7f58320416364b1d633b86662158176007d2cd41 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 07:18:13 -0800 Subject: [PATCH 03/26] Removed stable version list. --- pkg/Microsoft.Private.PackageBaseline/packageIndex.json | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json index a3e6e5d0f5b8..93cbd23e09c0 100644 --- a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json +++ b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json @@ -1478,7 +1478,6 @@ }, "System.Memory": { "StableVersions": [ - "4.0.0" ], "BaselineVersion": "4.4.0", "AssemblyVersionInPackageVersion": { From 372dd2ae9adb6331b3215e0e1c77c8e0acb36781 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 07:24:16 -0800 Subject: [PATCH 04/26] DangerousCreate gets more Dangerous... --- src/System.Memory/src/Resources/Strings.resx | 6 ----- src/System.Memory/src/System/Span.cs | 18 +++------------ .../tests/Span/DangerousCreate.cs | 23 ------------------- 3 files changed, 3 insertions(+), 44 deletions(-) diff --git a/src/System.Memory/src/Resources/Strings.resx b/src/System.Memory/src/Resources/Strings.resx index 2b3137a71805..e5bc8b905b71 100644 --- a/src/System.Memory/src/Resources/Strings.resx +++ b/src/System.Memory/src/Resources/Strings.resx @@ -129,12 +129,6 @@ Cannot use type '{0}'. Only value types without pointers or references are supported. - - Cannot pass arrays to this method. Use a constructor that takes an array argument. - - - Cannot pass strings to this method. - Destination is too short. diff --git a/src/System.Memory/src/System/Span.cs b/src/System.Memory/src/System/Span.cs index 4915b4172cb6..af703a5de649 100644 --- a/src/System.Memory/src/System/Span.cs +++ b/src/System.Memory/src/System/Span.cs @@ -127,12 +127,8 @@ public unsafe Span(void* pointer, int length) /// "length" is not checked, nor is the fact that "rawPointer" actually lies within the object. /// /// The managed object that contains the data to span over. - /// A reference to data within that object. + /// A reference to data within that object. /// The number of elements the memory contains. - /// - /// Thrown when the specified object is an array or string. Use the type-specific constructor - /// for these special cases. - /// /// /// Thrown when the specified object is null. /// @@ -140,23 +136,15 @@ public unsafe Span(void* pointer, int length) /// Thrown when the specified is negative. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span DangerousCreate(object obj, ref T rawPointer, int length) + public static Span DangerousCreate(object obj, ref T objectData, int length) { if (obj == null) throw new ArgumentNullException(nameof(obj)); if (length < 0) throw new ArgumentOutOfRangeException(nameof(length)); - // Disallow "special" objects that have their own object headers and would yield unexpected (and runtime-dependent) results. - // We already have constructors for arrays (and that enforce the passing of the properly typed single-dimensional array.) - // This constructor is not useful for strings (cannot express"ref s[i]"). - if (obj is Array) - throw new ArgumentException(SR.Argument_CannotPassArrayToDangerousCreate); - if (obj is string) - throw new ArgumentException(SR.Argument_CannotPassStringToDangerousCreate); - Pinnable pinnable = Unsafe.As>(obj); - IntPtr byteOffset = Unsafe.ByteOffset(ref pinnable.Data, ref rawPointer); + IntPtr byteOffset = Unsafe.ByteOffset(ref pinnable.Data, ref objectData); return new Span(pinnable, byteOffset, length); } diff --git a/src/System.Memory/tests/Span/DangerousCreate.cs b/src/System.Memory/tests/Span/DangerousCreate.cs index 04dcf7705f77..8188ee5052ae 100644 --- a/src/System.Memory/tests/Span/DangerousCreate.cs +++ b/src/System.Memory/tests/Span/DangerousCreate.cs @@ -21,29 +21,6 @@ public static void DangerousCreateNullObject() }); } - [Fact] - public static void DangerousCreateNoArrays() - { - Assert.Throws( - delegate () - { - int[] a = new int[4]; - Span.DangerousCreate(a, ref a[0], 0); - }); - } - - [Fact] - public static void DangerousCreateNoStrings() - { - Assert.Throws( - delegate () - { - char dummy = 'A'; - Span.DangerousCreate("Hello", ref dummy, 0); - }); - } - - [Fact] public static void DangerousCreateBadLength() { From 82ea8d593f2237e6b3bad517039bd901fdee0c1c Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 07:51:04 -0800 Subject: [PATCH 05/26] Updated dependency versions *again*. --- src/System.Memory/src/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Memory/src/project.json b/src/System.Memory/src/project.json index 3491f356642a..2eac5afe9d5b 100644 --- a/src/System.Memory/src/project.json +++ b/src/System.Memory/src/project.json @@ -5,7 +5,7 @@ "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Reflection": "4.1.0", - "System.Runtime.CompilerServices.Unsafe": "4.4.0-beta-24710-01" + "System.Runtime.CompilerServices.Unsafe": "4.4.0-beta-24711-01" }, "frameworks": { "netstandard1.0": {} From f25a42a137e58d144c6b14e1b253a404192f847a Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 07:57:56 -0800 Subject: [PATCH 06/26] IsReferenceFree fast-paths for common primitives. --- src/System.Memory/src/System/SpanHelpers.cs | 31 ++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/System.Memory/src/System/SpanHelpers.cs b/src/System.Memory/src/System/SpanHelpers.cs index 997e45d5259f..b6f848f46d89 100644 --- a/src/System.Memory/src/System/SpanHelpers.cs +++ b/src/System.Memory/src/System/SpanHelpers.cs @@ -52,7 +52,36 @@ public static IntPtr Add(this IntPtr start, int index) /// /// Determine if a type is eligible for storage in unmanaged memory. TODO: To be replaced by a ContainsReference() api. /// - public static bool IsReferenceFree() => PerTypeLatches.IsReferenceFree; + public static bool IsReferenceFree() + { + // Under the JIT, these become constant-folded. + if (typeof(T) == typeof(byte)) + return true; + if (typeof(T) == typeof(sbyte)) + return true; + if (typeof(T) == typeof(bool)) + return true; + if (typeof(T) == typeof(char)) + return true; + if (typeof(T) == typeof(short)) + return true; + if (typeof(T) == typeof(ushort)) + return true; + if (typeof(T) == typeof(int)) + return true; + if (typeof(T) == typeof(uint)) + return true; + if (typeof(T) == typeof(long)) + return true; + if (typeof(T) == typeof(ulong)) + return true; + if (typeof(T) == typeof(IntPtr)) + return true; + if (typeof(T) == typeof(UIntPtr)) + return true; + + return PerTypeLatches.IsReferenceFree; + } private static bool IsReferenceFree(Type type) { From c47176c1a1ea98e42fc87c894d7f923802b062e0 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 08:45:14 -0800 Subject: [PATCH 07/26] Move exception throwing out of inline code. --- src/System.Memory/src/System.Memory.csproj | 1 + src/System.Memory/src/System/Span.cs | 32 ++++++------ src/System.Memory/src/System/ThrowHelper.cs | 55 +++++++++++++++++++++ 3 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 src/System.Memory/src/System/ThrowHelper.cs diff --git a/src/System.Memory/src/System.Memory.csproj b/src/System.Memory/src/System.Memory.csproj index 744d95c3f955..1f064769ffe2 100644 --- a/src/System.Memory/src/System.Memory.csproj +++ b/src/System.Memory/src/System.Memory.csproj @@ -17,6 +17,7 @@ + diff --git a/src/System.Memory/src/System/Span.cs b/src/System.Memory/src/System/Span.cs index af703a5de649..2f11c9183961 100644 --- a/src/System.Memory/src/System/Span.cs +++ b/src/System.Memory/src/System/Span.cs @@ -28,9 +28,9 @@ public struct Span public Span(T[] array) { if (array == null) - throw new ArgumentNullException(nameof(array)); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) - throw new ArrayTypeMismatchException(SR.Format(SR.ArrayTypeMustBeExactMatch, typeof(T))); + ThrowHelper.ThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); _length = array.Length; _pinnable = Unsafe.As>(array); @@ -53,13 +53,13 @@ public Span(T[] array) public Span(T[] array, int start) { if (array == null) - throw new ArgumentNullException(nameof(array)); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) - throw new ArrayTypeMismatchException(SR.Format(SR.ArrayTypeMustBeExactMatch, typeof(T))); + ThrowHelper.ThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); int arrayLength = array.Length; if ((uint)start > (uint)arrayLength) - throw new ArgumentOutOfRangeException(nameof(start)); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); _length = arrayLength - start; _pinnable = Unsafe.As>(array); @@ -83,11 +83,11 @@ public Span(T[] array, int start) public Span(T[] array, int start, int length) { if (array == null) - throw new ArgumentNullException(nameof(array)); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) - throw new ArrayTypeMismatchException(SR.Format(SR.ArrayTypeMustBeExactMatch, typeof(T))); + ThrowHelper.ThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) - throw new ArgumentOutOfRangeException(nameof(start)); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); _length = length; _pinnable = Unsafe.As>(array); @@ -112,9 +112,9 @@ public Span(T[] array, int start, int length) public unsafe Span(void* pointer, int length) { if (!SpanHelpers.IsReferenceFree()) - throw new ArgumentException(SR.Format(SR.Argument_InvalidTypeWithPointersNotSupported, typeof(T))); + ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T)); if (length < 0) - throw new ArgumentOutOfRangeException(nameof(length)); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); _length = length; _pinnable = null; @@ -139,9 +139,9 @@ public unsafe Span(void* pointer, int length) public static Span DangerousCreate(object obj, ref T objectData, int length) { if (obj == null) - throw new ArgumentNullException(nameof(obj)); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj); if (length < 0) - throw new ArgumentOutOfRangeException(nameof(length)); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length); Pinnable pinnable = Unsafe.As>(obj); IntPtr byteOffset = Unsafe.ByteOffset(ref pinnable.Data, ref objectData); @@ -183,7 +183,7 @@ public ref T this[int index] get { if ((uint)index >= ((uint)_length)) - throw new IndexOutOfRangeException(); + ThrowHelper.ThrowIndexOutOfRangeException(); IntPtr adjustedByteOffset = _byteOffset.Add(index); if (_pinnable == null) @@ -206,7 +206,7 @@ public ref T this[int index] public void CopyTo(Span destination) { if (!TryCopyTo(destination)) - throw new ArgumentException(SR.Argument_DestinationTooShort); + ThrowHelper.ThrowArgumentException_DestinationTooShort(); } @@ -322,7 +322,7 @@ public override int GetHashCode() public Span Slice(int start) { if ((uint)start > (uint)_length) - throw new ArgumentOutOfRangeException(nameof(start)); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); IntPtr newOffset = _byteOffset.Add(start); int length = _length - start; @@ -341,7 +341,7 @@ public Span Slice(int start) public Span Slice(int start, int length) { if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) - throw new ArgumentOutOfRangeException(nameof(start)); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); IntPtr newOffset = _byteOffset.Add(start); return new Span(_pinnable, newOffset, length); diff --git a/src/System.Memory/src/System/ThrowHelper.cs b/src/System.Memory/src/System/ThrowHelper.cs new file mode 100644 index 000000000000..39c6c451d83a --- /dev/null +++ b/src/System.Memory/src/System/ThrowHelper.cs @@ -0,0 +1,55 @@ +// 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.Runtime.CompilerServices; + +namespace System +{ + internal static class ThrowHelper + { + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void ThrowArgumentNullException(ExceptionArgument argument) + { + throw new ArgumentNullException(argument.ToString()); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void ThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(Type type) + { + throw new ArrayTypeMismatchException(SR.Format(SR.ArrayTypeMustBeExactMatch, type)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void ThrowArgumentException_InvalidTypeWithPointersNotSupported(Type type) + { + throw new ArgumentException(SR.Format(SR.Argument_InvalidTypeWithPointersNotSupported, type)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void ThrowArgumentException_DestinationTooShort() + { + throw new ArgumentException(SR.Argument_DestinationTooShort); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void ThrowIndexOutOfRangeException() + { + throw new IndexOutOfRangeException(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) + { + throw new ArgumentOutOfRangeException(argument.ToString()); + } + } + + internal enum ExceptionArgument + { + array, + length, + start, + obj, + } +} \ No newline at end of file From 9f9665d57f1266b66f04214c737cf19e102e8c32 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 08:47:52 -0800 Subject: [PATCH 08/26] Remove Windows-only restriction. --- src/System.Memory/src/System.Memory.builds | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Memory/src/System.Memory.builds b/src/System.Memory/src/System.Memory.builds index 2b9a9e3e3cfb..441802d2477b 100644 --- a/src/System.Memory/src/System.Memory.builds +++ b/src/System.Memory/src/System.Memory.builds @@ -2,7 +2,7 @@ - + From 79c4f3f81f811ee25982e1038edbce4ee70ed4eb Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 08:50:12 -0800 Subject: [PATCH 09/26] Remove baseline version. --- pkg/Microsoft.Private.PackageBaseline/packageIndex.json | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json index 93cbd23e09c0..00e6288c6a7f 100644 --- a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json +++ b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json @@ -1479,7 +1479,6 @@ "System.Memory": { "StableVersions": [ ], - "BaselineVersion": "4.4.0", "AssemblyVersionInPackageVersion": { "4.0.0.0": "4.0.0", "4.0.2.0": "4.3.0", From bde890eedfd9c3903523c8f38b504f3261170f37 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 09:36:29 -0800 Subject: [PATCH 10/26] Don't hardcode array header size. --- src/System.Memory/src/System.Memory.csproj | 1 - src/System.Memory/src/System/ArrayHeader.cs | 20 -------------------- src/System.Memory/src/System/Span.cs | 8 ++++---- src/System.Memory/src/System/SpanHelpers.cs | 18 +++++++++++------- 4 files changed, 15 insertions(+), 32 deletions(-) delete mode 100644 src/System.Memory/src/System/ArrayHeader.cs diff --git a/src/System.Memory/src/System.Memory.csproj b/src/System.Memory/src/System.Memory.csproj index 1f064769ffe2..370ed1b5663f 100644 --- a/src/System.Memory/src/System.Memory.csproj +++ b/src/System.Memory/src/System.Memory.csproj @@ -13,7 +13,6 @@ - diff --git a/src/System.Memory/src/System/ArrayHeader.cs b/src/System.Memory/src/System/ArrayHeader.cs deleted file mode 100644 index de62e808c9a3..000000000000 --- a/src/System.Memory/src/System/ArrayHeader.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.InteropServices; - -namespace System -{ - // - // This is a "magic constant" that defines how much offset to add to Pinnable.Data to get to the first element of an array. - // This is necessarily non-portable, but it works for the CLR and CoreClr. It does not work for Mono (Mono has an extra Bounds - // pointer) but Mono wants to do Span intrinsically. - // - [StructLayout(LayoutKind.Sequential)] - internal struct ArrayHeader - { - private IntPtr _lengthPlusPadding; - } -} diff --git a/src/System.Memory/src/System/Span.cs b/src/System.Memory/src/System/Span.cs index 2f11c9183961..2a295c6ed969 100644 --- a/src/System.Memory/src/System/Span.cs +++ b/src/System.Memory/src/System/Span.cs @@ -34,7 +34,7 @@ public Span(T[] array) _length = array.Length; _pinnable = Unsafe.As>(array); - unsafe { _byteOffset = (IntPtr)sizeof(ArrayHeader); } + _byteOffset = SpanHelpers.PerTypeValues.ArrayAdjustment; } /// @@ -63,7 +63,7 @@ public Span(T[] array, int start) _length = arrayLength - start; _pinnable = Unsafe.As>(array); - unsafe { _byteOffset = ((IntPtr)sizeof(ArrayHeader)).Add(start); } + _byteOffset = SpanHelpers.PerTypeValues.ArrayAdjustment.Add(start); } /// @@ -91,7 +91,7 @@ public Span(T[] array, int start, int length) _length = length; _pinnable = Unsafe.As>(array); - unsafe { _byteOffset = ((IntPtr)sizeof(ArrayHeader)).Add(start); } + _byteOffset = SpanHelpers.PerTypeValues.ArrayAdjustment.Add(start); } /// @@ -355,7 +355,7 @@ public Span Slice(int start, int length) public T[] ToArray() { if (_length == 0) - return SpanHelpers.EmptyArray(); + return SpanHelpers.PerTypeValues.EmptyArray; T[] result = new T[_length]; CopyTo(result); diff --git a/src/System.Memory/src/System/SpanHelpers.cs b/src/System.Memory/src/System/SpanHelpers.cs index b6f848f46d89..cbd0a80eee9d 100644 --- a/src/System.Memory/src/System/SpanHelpers.cs +++ b/src/System.Memory/src/System/SpanHelpers.cs @@ -44,11 +44,6 @@ public static IntPtr Add(this IntPtr start, int index) } } - /// - /// Returns a cached empty array (Array.Empty not available on S.R. 4.0.0.0) - /// - public static T[] EmptyArray() => PerTypeLatches.EmptyArray; - /// /// Determine if a type is eligible for storage in unmanaged memory. TODO: To be replaced by a ContainsReference() api. /// @@ -80,7 +75,7 @@ public static bool IsReferenceFree() if (typeof(T) == typeof(UIntPtr)) return true; - return PerTypeLatches.IsReferenceFree; + return PerTypeValues.IsReferenceFree; } private static bool IsReferenceFree(Type type) @@ -109,7 +104,14 @@ private static bool IsReferenceFree(Type type) return true; } - private static class PerTypeLatches + // Array header sizes are a runtime implementation detail and aren't the same across all runtimes. (The CLR made a tweak after 4.5, and Mono has an extra Bounds pointer.) + private static IntPtr MeasureArrayAdjustment() + { + T[] sampleArray = new T[1]; + return Unsafe.ByteOffset(ref Unsafe.As>(sampleArray).Data, ref sampleArray[0]); + } + + public static class PerTypeValues { // // Latch to ensure that excruciatingly expensive validation check for constructing a Span around a raw pointer is done @@ -121,6 +123,8 @@ private static class PerTypeLatches public static readonly bool IsReferenceFree = IsReferenceFree(typeof(T)); public static readonly T[] EmptyArray = new T[0]; + + public static readonly IntPtr ArrayAdjustment = MeasureArrayAdjustment(); } } } From 1c400cfd1d9f6dfb02de833055069f5c9764c5e1 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 09:47:56 -0800 Subject: [PATCH 11/26] Another dependency version change! What's the matter, don't we have any version numbers you like?? --- src/System.Memory/src/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Memory/src/project.json b/src/System.Memory/src/project.json index 2eac5afe9d5b..a72d46e386ae 100644 --- a/src/System.Memory/src/project.json +++ b/src/System.Memory/src/project.json @@ -5,7 +5,7 @@ "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Reflection": "4.1.0", - "System.Runtime.CompilerServices.Unsafe": "4.4.0-beta-24711-01" + "System.Runtime.CompilerServices.Unsafe": "4.4.0-beta-24711-02" }, "frameworks": { "netstandard1.0": {} From 7817577e38d1af48399d3881a7d88dc16e0f99a7 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 09:55:36 -0800 Subject: [PATCH 12/26] Version => 4.0.0.0 --- pkg/Microsoft.Private.PackageBaseline/packageIndex.json | 4 +--- src/System.Memory/src/System.Memory.csproj | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json index 00e6288c6a7f..3267aaada3da 100644 --- a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json +++ b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json @@ -1480,9 +1480,7 @@ "StableVersions": [ ], "AssemblyVersionInPackageVersion": { - "4.0.0.0": "4.0.0", - "4.0.2.0": "4.3.0", - "4.0.3.0": "4.4.0" + "4.0.0.0": "4.4.0" } }, "System.Net.Http": { diff --git a/src/System.Memory/src/System.Memory.csproj b/src/System.Memory/src/System.Memory.csproj index 370ed1b5663f..a53e3efed34b 100644 --- a/src/System.Memory/src/System.Memory.csproj +++ b/src/System.Memory/src/System.Memory.csproj @@ -4,7 +4,7 @@ {4BBC8F69-D03E-4432-AA8A-D458FA5B235A} System.Memory - 4.0.3.0 + 4.0.0.0 true false .NETStandard,Version=v1.0 From 85fe48f48bdd6f6c1e0e1a410856c4f71410eea3 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 10:06:13 -0800 Subject: [PATCH 13/26] Use Unsafe.Add rather than SpanHeloer.Add in this[index] (all this repeated source is annoying - unfortunately, Rosylin at this point at least, insists that "ref" variables be initialized once only at declaration point and doesn't allow the conditional ternary operator on ref types. This makes it hard to manually inline DangerousGetPinnableReference in a nice way...) --- src/System.Memory/src/System/Span.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/System.Memory/src/System/Span.cs b/src/System.Memory/src/System/Span.cs index 2a295c6ed969..c0727ffbcad0 100644 --- a/src/System.Memory/src/System/Span.cs +++ b/src/System.Memory/src/System/Span.cs @@ -185,11 +185,10 @@ public ref T this[int index] if ((uint)index >= ((uint)_length)) ThrowHelper.ThrowIndexOutOfRangeException(); - IntPtr adjustedByteOffset = _byteOffset.Add(index); if (_pinnable == null) - unsafe { return ref Unsafe.AsRef(adjustedByteOffset.ToPointer()); } + unsafe { return ref Unsafe.Add(ref Unsafe.AsRef(_byteOffset.ToPointer()), index); } else - return ref Unsafe.AddByteOffset(ref _pinnable.Data, adjustedByteOffset); + return ref Unsafe.Add(ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset), index); } } From b4c7015e92ecef8c58e3464fbec16e7ff1cff878 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 10:14:37 -0800 Subject: [PATCH 14/26] ThrowHelper.Throw ==> throw ThrowHelper.Create Allow JIT to recognize the throw path as a no-return path so it can throw it into the cold region. --- src/System.Memory/src/System/Span.cs | 32 ++++++++++----------- src/System.Memory/src/System/ThrowHelper.cs | 24 ++++++++-------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/System.Memory/src/System/Span.cs b/src/System.Memory/src/System/Span.cs index c0727ffbcad0..8fdad9fe8e3b 100644 --- a/src/System.Memory/src/System/Span.cs +++ b/src/System.Memory/src/System/Span.cs @@ -28,9 +28,9 @@ public struct Span public Span(T[] array) { if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + throw ThrowHelper.CreateThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) - ThrowHelper.ThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); + throw ThrowHelper.CreateThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); _length = array.Length; _pinnable = Unsafe.As>(array); @@ -53,13 +53,13 @@ public Span(T[] array) public Span(T[] array, int start) { if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + throw ThrowHelper.CreateThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) - ThrowHelper.ThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); + throw ThrowHelper.CreateThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); int arrayLength = array.Length; if ((uint)start > (uint)arrayLength) - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + throw ThrowHelper.CreateThrowArgumentOutOfRangeException(ExceptionArgument.start); _length = arrayLength - start; _pinnable = Unsafe.As>(array); @@ -83,11 +83,11 @@ public Span(T[] array, int start) public Span(T[] array, int start, int length) { if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + throw ThrowHelper.CreateThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) - ThrowHelper.ThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); + throw ThrowHelper.CreateThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + throw ThrowHelper.CreateThrowArgumentOutOfRangeException(ExceptionArgument.start); _length = length; _pinnable = Unsafe.As>(array); @@ -112,9 +112,9 @@ public Span(T[] array, int start, int length) public unsafe Span(void* pointer, int length) { if (!SpanHelpers.IsReferenceFree()) - ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T)); + throw ThrowHelper.CreateThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T)); if (length < 0) - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + throw ThrowHelper.CreateThrowArgumentOutOfRangeException(ExceptionArgument.start); _length = length; _pinnable = null; @@ -139,9 +139,9 @@ public unsafe Span(void* pointer, int length) public static Span DangerousCreate(object obj, ref T objectData, int length) { if (obj == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj); + throw ThrowHelper.CreateThrowArgumentNullException(ExceptionArgument.obj); if (length < 0) - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length); + throw ThrowHelper.CreateThrowArgumentOutOfRangeException(ExceptionArgument.length); Pinnable pinnable = Unsafe.As>(obj); IntPtr byteOffset = Unsafe.ByteOffset(ref pinnable.Data, ref objectData); @@ -183,7 +183,7 @@ public ref T this[int index] get { if ((uint)index >= ((uint)_length)) - ThrowHelper.ThrowIndexOutOfRangeException(); + throw ThrowHelper.CreateThrowIndexOutOfRangeException(); if (_pinnable == null) unsafe { return ref Unsafe.Add(ref Unsafe.AsRef(_byteOffset.ToPointer()), index); } @@ -205,7 +205,7 @@ public ref T this[int index] public void CopyTo(Span destination) { if (!TryCopyTo(destination)) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); + throw ThrowHelper.CreateThrowArgumentException_DestinationTooShort(); } @@ -321,7 +321,7 @@ public override int GetHashCode() public Span Slice(int start) { if ((uint)start > (uint)_length) - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + throw ThrowHelper.CreateThrowArgumentOutOfRangeException(ExceptionArgument.start); IntPtr newOffset = _byteOffset.Add(start); int length = _length - start; @@ -340,7 +340,7 @@ public Span Slice(int start) public Span Slice(int start, int length) { if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + throw ThrowHelper.CreateThrowArgumentOutOfRangeException(ExceptionArgument.start); IntPtr newOffset = _byteOffset.Add(start); return new Span(_pinnable, newOffset, length); diff --git a/src/System.Memory/src/System/ThrowHelper.cs b/src/System.Memory/src/System/ThrowHelper.cs index 39c6c451d83a..d421c55d4a59 100644 --- a/src/System.Memory/src/System/ThrowHelper.cs +++ b/src/System.Memory/src/System/ThrowHelper.cs @@ -9,39 +9,39 @@ namespace System internal static class ThrowHelper { [MethodImpl(MethodImplOptions.NoInlining)] - internal static void ThrowArgumentNullException(ExceptionArgument argument) + internal static Exception CreateThrowArgumentNullException(ExceptionArgument argument) { - throw new ArgumentNullException(argument.ToString()); + return new ArgumentNullException(argument.ToString()); } [MethodImpl(MethodImplOptions.NoInlining)] - internal static void ThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(Type type) + internal static Exception CreateThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(Type type) { - throw new ArrayTypeMismatchException(SR.Format(SR.ArrayTypeMustBeExactMatch, type)); + return new ArrayTypeMismatchException(SR.Format(SR.ArrayTypeMustBeExactMatch, type)); } [MethodImpl(MethodImplOptions.NoInlining)] - internal static void ThrowArgumentException_InvalidTypeWithPointersNotSupported(Type type) + internal static Exception CreateThrowArgumentException_InvalidTypeWithPointersNotSupported(Type type) { - throw new ArgumentException(SR.Format(SR.Argument_InvalidTypeWithPointersNotSupported, type)); + return new ArgumentException(SR.Format(SR.Argument_InvalidTypeWithPointersNotSupported, type)); } [MethodImpl(MethodImplOptions.NoInlining)] - internal static void ThrowArgumentException_DestinationTooShort() + internal static Exception CreateThrowArgumentException_DestinationTooShort() { - throw new ArgumentException(SR.Argument_DestinationTooShort); + return new ArgumentException(SR.Argument_DestinationTooShort); } [MethodImpl(MethodImplOptions.NoInlining)] - internal static void ThrowIndexOutOfRangeException() + internal static Exception CreateThrowIndexOutOfRangeException() { - throw new IndexOutOfRangeException(); + return new IndexOutOfRangeException(); } [MethodImpl(MethodImplOptions.NoInlining)] - internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) + internal static Exception CreateThrowArgumentOutOfRangeException(ExceptionArgument argument) { - throw new ArgumentOutOfRangeException(argument.ToString()); + return new ArgumentOutOfRangeException(argument.ToString()); } } From 7ee7dcf1347ca7f7ef7b4e1ff4801785aaa97e18 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Fri, 11 Nov 2016 10:46:11 -0800 Subject: [PATCH 15/26] Revert the specific primitive type checks in IsReferenceFree(). It pessimizes the routine on the desktop CLR. --- src/System.Memory/src/System/SpanHelpers.cs | 31 +-------------------- 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/src/System.Memory/src/System/SpanHelpers.cs b/src/System.Memory/src/System/SpanHelpers.cs index cbd0a80eee9d..e412a2c3ac89 100644 --- a/src/System.Memory/src/System/SpanHelpers.cs +++ b/src/System.Memory/src/System/SpanHelpers.cs @@ -47,36 +47,7 @@ public static IntPtr Add(this IntPtr start, int index) /// /// Determine if a type is eligible for storage in unmanaged memory. TODO: To be replaced by a ContainsReference() api. /// - public static bool IsReferenceFree() - { - // Under the JIT, these become constant-folded. - if (typeof(T) == typeof(byte)) - return true; - if (typeof(T) == typeof(sbyte)) - return true; - if (typeof(T) == typeof(bool)) - return true; - if (typeof(T) == typeof(char)) - return true; - if (typeof(T) == typeof(short)) - return true; - if (typeof(T) == typeof(ushort)) - return true; - if (typeof(T) == typeof(int)) - return true; - if (typeof(T) == typeof(uint)) - return true; - if (typeof(T) == typeof(long)) - return true; - if (typeof(T) == typeof(ulong)) - return true; - if (typeof(T) == typeof(IntPtr)) - return true; - if (typeof(T) == typeof(UIntPtr)) - return true; - - return PerTypeValues.IsReferenceFree; - } + public static bool IsReferenceFree() => PerTypeValues.IsReferenceFree; private static bool IsReferenceFree(Type type) { From 1ac39798b1e535bdaa64c5162b57c52b92313d5e Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Mon, 14 Nov 2016 07:38:19 -0800 Subject: [PATCH 16/26] Fix DangerousGetPinnableReference to be a method to match spec. --- src/System.Memory/src/System/Span.cs | 21 ++++++++----------- .../tests/Span/CtorPointerInt.cs | 2 +- .../Span/DangerousGetPinnableReference.cs | 14 ++++++------- src/System.Memory/tests/Span/Empty.cs | 2 +- src/System.Memory/tests/Span/Slice.cs | 4 ++-- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/System.Memory/src/System/Span.cs b/src/System.Memory/src/System/Span.cs index 8fdad9fe8e3b..b1499bb8a1e4 100644 --- a/src/System.Memory/src/System/Span.cs +++ b/src/System.Memory/src/System/Span.cs @@ -226,8 +226,8 @@ public bool TryCopyTo(Span destination) // TODO: This is a tide-over implementation as we plan to add a overlap-safe cpblk-based api to Unsafe. (https://github.com/dotnet/corefx/issues/13427) unsafe { - ref T src = ref DangerousGetPinnableReference; - ref T dst = ref destination.DangerousGetPinnableReference; + ref T src = ref DangerousGetPinnableReference(); + ref T dst = ref destination.DangerousGetPinnableReference(); IntPtr srcMinusDst = Unsafe.ByteOffset(ref dst, ref src); int length = _length; @@ -262,7 +262,7 @@ public bool TryCopyTo(Span destination) if (left._length != right._length) return false; - if (!Unsafe.AreSame(ref left.DangerousGetPinnableReference, ref right.DangerousGetPinnableReference)) + if (!Unsafe.AreSame(ref left.DangerousGetPinnableReference(), ref right.DangerousGetPinnableReference())) return false; return true; @@ -370,16 +370,13 @@ public T[] ToArray() /// 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 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T DangerousGetPinnableReference() { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - if (_pinnable == null) - unsafe { return ref Unsafe.AsRef(_byteOffset.ToPointer()); } - else - return ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset); - } + if (_pinnable == null) + unsafe { return ref Unsafe.AsRef(_byteOffset.ToPointer()); } + else + return ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset); } // diff --git a/src/System.Memory/tests/Span/CtorPointerInt.cs b/src/System.Memory/tests/Span/CtorPointerInt.cs index 9b4671897162..289b882c160b 100644 --- a/src/System.Memory/tests/Span/CtorPointerInt.cs +++ b/src/System.Memory/tests/Span/CtorPointerInt.cs @@ -33,7 +33,7 @@ public static void CtorPointerNull() { Span span = new Span((void*)null, 0); span.Validate(); - Assert.True(Unsafe.AreSame(ref Unsafe.AsRef((void*)null), ref span.DangerousGetPinnableReference)); + Assert.True(Unsafe.AreSame(ref Unsafe.AsRef((void*)null), ref span.DangerousGetPinnableReference())); } } diff --git a/src/System.Memory/tests/Span/DangerousGetPinnableReference.cs b/src/System.Memory/tests/Span/DangerousGetPinnableReference.cs index 40119f7830fe..199e7048a38b 100644 --- a/src/System.Memory/tests/Span/DangerousGetPinnableReference.cs +++ b/src/System.Memory/tests/Span/DangerousGetPinnableReference.cs @@ -15,19 +15,19 @@ public static void DangerousGetPinnableReferenceArray() { int[] a = { 91, 92, 93, 94, 95 }; Span span = new Span(a, 1, 3); - ref int pinnableReference = ref span.DangerousGetPinnableReference; + ref int pinnableReference = ref span.DangerousGetPinnableReference(); Assert.True(Unsafe.AreSame(ref a[1], ref pinnableReference)); } [Fact] public static void DangerousGetPinnableReferenceArrayPastEnd() { - // The only real difference between DangerousGetPinnableReference and "ref span[0]" is that - // DangerousGetPinnableReference of a zero-length won't throw an IndexOutOfRange. + // The only real difference between DangerousGetPinnableReference() and "ref span[0]" is that + // DangerousGetPinnableReference() of a zero-length won't throw an IndexOutOfRange. int[] a = { 91, 92, 93, 94, 95 }; Span span = new Span(a, a.Length, 0); - ref int pinnableReference = ref span.DangerousGetPinnableReference; + ref int pinnableReference = ref span.DangerousGetPinnableReference(); ref int expected = ref Unsafe.Add(ref a[a.Length - 1], 1); Assert.True(Unsafe.AreSame(ref expected, ref pinnableReference)); } @@ -39,7 +39,7 @@ public static void DangerousGetPinnableReferencePointer() { int i = 42; Span span = new Span(&i, 1); - ref int pinnableReference = ref span.DangerousGetPinnableReference; + ref int pinnableReference = ref span.DangerousGetPinnableReference(); Assert.True(Unsafe.AreSame(ref i, ref pinnableReference)); } } @@ -50,7 +50,7 @@ public static void DangerousGetPinnableReferencePointerDangerousCreate1() TestClass testClass = new TestClass(); Span span = Span.DangerousCreate(testClass, ref testClass.C1, 3); - ref char pinnableReference = ref span.DangerousGetPinnableReference; + ref char pinnableReference = ref span.DangerousGetPinnableReference(); Assert.True(Unsafe.AreSame(ref testClass.C1, ref pinnableReference)); } @@ -60,7 +60,7 @@ public static void DangerousGetPinnableReferenceEmpty() unsafe { Span span = Span.Empty; - ref int pinnableReference = ref span.DangerousGetPinnableReference; + ref int pinnableReference = ref span.DangerousGetPinnableReference(); Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(null), ref pinnableReference)); } } diff --git a/src/System.Memory/tests/Span/Empty.cs b/src/System.Memory/tests/Span/Empty.cs index 9d6ed11e9f55..50e2e5196b55 100644 --- a/src/System.Memory/tests/Span/Empty.cs +++ b/src/System.Memory/tests/Span/Empty.cs @@ -18,7 +18,7 @@ public static void Empty() unsafe { ref int expected = ref Unsafe.AsRef(null); - ref int actual = ref empty.DangerousGetPinnableReference; + ref int actual = ref empty.DangerousGetPinnableReference(); Assert.True(Unsafe.AreSame(ref expected, ref actual)); } } diff --git a/src/System.Memory/tests/Span/Slice.cs b/src/System.Memory/tests/Span/Slice.cs index fd45199f2917..93a7217938bd 100644 --- a/src/System.Memory/tests/Span/Slice.cs +++ b/src/System.Memory/tests/Span/Slice.cs @@ -24,7 +24,7 @@ public static void SliceIntPastEnd() int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }; Span span = new Span(a).Slice(a.Length); Assert.Equal(0, span.Length); - Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref span.DangerousGetPinnableReference, 1))); + Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref span.DangerousGetPinnableReference(), 1))); } [Fact] @@ -51,7 +51,7 @@ public static void SliceIntIntPastEnd() int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }; Span span = new Span(a).Slice(a.Length, 0); Assert.Equal(0, span.Length); - Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref span.DangerousGetPinnableReference, 1))); + Assert.True(Unsafe.AreSame(ref a[a.Length - 1], ref Unsafe.Subtract(ref span.DangerousGetPinnableReference(), 1))); } [Fact] From dce1728b1bd57c0165ac5e15367bca4cd964f7a9 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Mon, 14 Nov 2016 07:47:22 -0800 Subject: [PATCH 17/26] PR feedback. Description: "Provide classes" => "Provide types" Add fastpath for specific types in IsReferenceFree Make MeasureArrayAdjustment a PerTypeValues private --- pkg/descriptions.json | 2 +- src/System.Memory/src/System/SpanHelpers.cs | 53 ++++++++++++++++----- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/pkg/descriptions.json b/pkg/descriptions.json index 68862efa6857..cddcf18a78ee 100644 --- a/pkg/descriptions.json +++ b/pkg/descriptions.json @@ -835,7 +835,7 @@ }, { "Name": "System.Memory", - "Description": "Provides classes for efficient low-allocation access to memory.", + "Description": "Provides types for efficient low-allocation access to memory.", "CommonTypes": [ "System.Span", ] diff --git a/src/System.Memory/src/System/SpanHelpers.cs b/src/System.Memory/src/System/SpanHelpers.cs index e412a2c3ac89..96a5adbccfe7 100644 --- a/src/System.Memory/src/System/SpanHelpers.cs +++ b/src/System.Memory/src/System/SpanHelpers.cs @@ -49,7 +49,38 @@ public static IntPtr Add(this IntPtr start, int index) /// public static bool IsReferenceFree() => PerTypeValues.IsReferenceFree; - private static bool IsReferenceFree(Type type) + private static bool IsReferenceFreeCore() + { + // Under the JIT, these become constant-folded. + if (typeof(T) == typeof(byte)) + return true; + if (typeof(T) == typeof(sbyte)) + return true; + if (typeof(T) == typeof(bool)) + return true; + if (typeof(T) == typeof(char)) + return true; + if (typeof(T) == typeof(short)) + return true; + if (typeof(T) == typeof(ushort)) + return true; + if (typeof(T) == typeof(int)) + return true; + if (typeof(T) == typeof(uint)) + return true; + if (typeof(T) == typeof(long)) + return true; + if (typeof(T) == typeof(ulong)) + return true; + if (typeof(T) == typeof(IntPtr)) + return true; + if (typeof(T) == typeof(UIntPtr)) + return true; + + return IsReferenceFreeCoreSlow(typeof(T)); + } + + private static bool IsReferenceFreeCoreSlow(Type type) { if (type.GetTypeInfo().IsPrimitive) // This is hopefully the common case. All types that return true for this are value types w/out embedded references. return true; @@ -69,19 +100,12 @@ private static bool IsReferenceFree(Type type) { if (field.IsStatic) continue; - if (!IsReferenceFree(field.FieldType)) + if (!IsReferenceFreeCoreSlow(field.FieldType)) return false; } return true; } - // Array header sizes are a runtime implementation detail and aren't the same across all runtimes. (The CLR made a tweak after 4.5, and Mono has an extra Bounds pointer.) - private static IntPtr MeasureArrayAdjustment() - { - T[] sampleArray = new T[1]; - return Unsafe.ByteOffset(ref Unsafe.As>(sampleArray).Data, ref sampleArray[0]); - } - public static class PerTypeValues { // @@ -91,11 +115,18 @@ public static class PerTypeValues // false == not yet computed or found to be not reference free. // true == confirmed reference free // - public static readonly bool IsReferenceFree = IsReferenceFree(typeof(T)); + public static readonly bool IsReferenceFree = IsReferenceFreeCore(); public static readonly T[] EmptyArray = new T[0]; - public static readonly IntPtr ArrayAdjustment = MeasureArrayAdjustment(); + public static readonly IntPtr ArrayAdjustment = MeasureArrayAdjustment(); + + // Array header sizes are a runtime implementation detail and aren't the same across all runtimes. (The CLR made a tweak after 4.5, and Mono has an extra Bounds pointer.) + private static IntPtr MeasureArrayAdjustment() + { + T[] sampleArray = new T[1]; + return Unsafe.ByteOffset(ref Unsafe.As>(sampleArray).Data, ref sampleArray[0]); + } } } } From 3155862cbce4e04a540c7b141f6ce7f7a0aeffc0 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Mon, 14 Nov 2016 08:36:54 -0800 Subject: [PATCH 18/26] More concise operator== Eh... hard to tell with all the noise but it did seems to shave a couple of percentage points off. --- src/System.Memory/src/System/Span.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/System.Memory/src/System/Span.cs b/src/System.Memory/src/System/Span.cs index b1499bb8a1e4..5a07e06ce330 100644 --- a/src/System.Memory/src/System/Span.cs +++ b/src/System.Memory/src/System/Span.cs @@ -259,13 +259,7 @@ public bool TryCopyTo(Span destination) /// public static bool operator ==(Span left, Span right) { - if (left._length != right._length) - return false; - - if (!Unsafe.AreSame(ref left.DangerousGetPinnableReference(), ref right.DangerousGetPinnableReference())) - return false; - - return true; + return left._length == right._length && Unsafe.AreSame(ref left.DangerousGetPinnableReference(), ref right.DangerousGetPinnableReference()); } /// From 3334c945084f6ad328b9ff91987138e20c8aa42b Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Mon, 14 Nov 2016 09:16:23 -0800 Subject: [PATCH 19/26] Follow Ben's suggested ThrowHelper pattern. --- src/System.Memory/src/System/Span.cs | 32 ++++++++-------- src/System.Memory/src/System/ThrowHelper.cs | 42 +++++++++------------ 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/src/System.Memory/src/System/Span.cs b/src/System.Memory/src/System/Span.cs index 5a07e06ce330..9e6810012357 100644 --- a/src/System.Memory/src/System/Span.cs +++ b/src/System.Memory/src/System/Span.cs @@ -28,9 +28,9 @@ public struct Span public Span(T[] array) { if (array == null) - throw ThrowHelper.CreateThrowArgumentNullException(ExceptionArgument.array); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) - throw ThrowHelper.CreateThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); + ThrowHelper.ThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); _length = array.Length; _pinnable = Unsafe.As>(array); @@ -53,13 +53,13 @@ public Span(T[] array) public Span(T[] array, int start) { if (array == null) - throw ThrowHelper.CreateThrowArgumentNullException(ExceptionArgument.array); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) - throw ThrowHelper.CreateThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); + ThrowHelper.ThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); int arrayLength = array.Length; if ((uint)start > (uint)arrayLength) - throw ThrowHelper.CreateThrowArgumentOutOfRangeException(ExceptionArgument.start); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); _length = arrayLength - start; _pinnable = Unsafe.As>(array); @@ -83,11 +83,11 @@ public Span(T[] array, int start) public Span(T[] array, int start, int length) { if (array == null) - throw ThrowHelper.CreateThrowArgumentNullException(ExceptionArgument.array); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (default(T) == null && array.GetType() != typeof(T[])) - throw ThrowHelper.CreateThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); + ThrowHelper.ThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(typeof(T)); if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) - throw ThrowHelper.CreateThrowArgumentOutOfRangeException(ExceptionArgument.start); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); _length = length; _pinnable = Unsafe.As>(array); @@ -112,9 +112,9 @@ public Span(T[] array, int start, int length) public unsafe Span(void* pointer, int length) { if (!SpanHelpers.IsReferenceFree()) - throw ThrowHelper.CreateThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T)); + ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T)); if (length < 0) - throw ThrowHelper.CreateThrowArgumentOutOfRangeException(ExceptionArgument.start); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); _length = length; _pinnable = null; @@ -139,9 +139,9 @@ public unsafe Span(void* pointer, int length) public static Span DangerousCreate(object obj, ref T objectData, int length) { if (obj == null) - throw ThrowHelper.CreateThrowArgumentNullException(ExceptionArgument.obj); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj); if (length < 0) - throw ThrowHelper.CreateThrowArgumentOutOfRangeException(ExceptionArgument.length); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length); Pinnable pinnable = Unsafe.As>(obj); IntPtr byteOffset = Unsafe.ByteOffset(ref pinnable.Data, ref objectData); @@ -183,7 +183,7 @@ public ref T this[int index] get { if ((uint)index >= ((uint)_length)) - throw ThrowHelper.CreateThrowIndexOutOfRangeException(); + ThrowHelper.ThrowIndexOutOfRangeException(); if (_pinnable == null) unsafe { return ref Unsafe.Add(ref Unsafe.AsRef(_byteOffset.ToPointer()), index); } @@ -205,7 +205,7 @@ public ref T this[int index] public void CopyTo(Span destination) { if (!TryCopyTo(destination)) - throw ThrowHelper.CreateThrowArgumentException_DestinationTooShort(); + ThrowHelper.ThrowArgumentException_DestinationTooShort(); } @@ -315,7 +315,7 @@ public override int GetHashCode() public Span Slice(int start) { if ((uint)start > (uint)_length) - throw ThrowHelper.CreateThrowArgumentOutOfRangeException(ExceptionArgument.start); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); IntPtr newOffset = _byteOffset.Add(start); int length = _length - start; @@ -334,7 +334,7 @@ public Span Slice(int start) public Span Slice(int start, int length) { if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) - throw ThrowHelper.CreateThrowArgumentOutOfRangeException(ExceptionArgument.start); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); IntPtr newOffset = _byteOffset.Add(start); return new Span(_pinnable, newOffset, length); diff --git a/src/System.Memory/src/System/ThrowHelper.cs b/src/System.Memory/src/System/ThrowHelper.cs index d421c55d4a59..0ec4f30927c0 100644 --- a/src/System.Memory/src/System/ThrowHelper.cs +++ b/src/System.Memory/src/System/ThrowHelper.cs @@ -6,43 +6,37 @@ namespace System { + // + // This pattern of inlinable "void Throw" routines that stack on top of NoInlining factory methods is a compromise between older JITs and newer JITs. + // The package this file appears in is targeted specifically at older JITs. Older JITs cannot inline methods that have conditional throws in them, + // thus we use these helpers to hide the throws. Newer JIT's will inline the Throw routines and will benefit from the code size reductions of + // splitting the creation details into their own non-inlinable factory methods. + // internal static class ThrowHelper { + internal static void ThrowArgumentNullException(ExceptionArgument argument) { throw CreateArgumentNullException(argument); } [MethodImpl(MethodImplOptions.NoInlining)] - internal static Exception CreateThrowArgumentNullException(ExceptionArgument argument) - { - return new ArgumentNullException(argument.ToString()); - } + private static Exception CreateArgumentNullException(ExceptionArgument argument) { return new ArgumentNullException(argument.ToString()); } + internal static void ThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(Type type) { throw CreateArrayTypeMismatchException_ArrayTypeMustBeExactMatch(type); } [MethodImpl(MethodImplOptions.NoInlining)] - internal static Exception CreateThrowArrayTypeMismatchException_ArrayTypeMustBeExactMatch(Type type) - { - return new ArrayTypeMismatchException(SR.Format(SR.ArrayTypeMustBeExactMatch, type)); - } + private static Exception CreateArrayTypeMismatchException_ArrayTypeMustBeExactMatch(Type type) { return new ArrayTypeMismatchException(SR.Format(SR.ArrayTypeMustBeExactMatch, type)); } + internal static void ThrowArgumentException_InvalidTypeWithPointersNotSupported(Type type) { throw CreateArgumentException_InvalidTypeWithPointersNotSupported(type); } [MethodImpl(MethodImplOptions.NoInlining)] - internal static Exception CreateThrowArgumentException_InvalidTypeWithPointersNotSupported(Type type) - { - return new ArgumentException(SR.Format(SR.Argument_InvalidTypeWithPointersNotSupported, type)); - } + private static Exception CreateArgumentException_InvalidTypeWithPointersNotSupported(Type type) { return new ArgumentException(SR.Format(SR.Argument_InvalidTypeWithPointersNotSupported, type)); } + internal static void ThrowArgumentException_DestinationTooShort() { throw CreateArgumentException_DestinationTooShort(); } [MethodImpl(MethodImplOptions.NoInlining)] - internal static Exception CreateThrowArgumentException_DestinationTooShort() - { - return new ArgumentException(SR.Argument_DestinationTooShort); - } + private static Exception CreateArgumentException_DestinationTooShort() { return new ArgumentException(SR.Argument_DestinationTooShort); } + internal static void ThrowIndexOutOfRangeException() { throw CreateIndexOutOfRangeException(); } [MethodImpl(MethodImplOptions.NoInlining)] - internal static Exception CreateThrowIndexOutOfRangeException() - { - return new IndexOutOfRangeException(); - } + private static Exception CreateIndexOutOfRangeException() { return new IndexOutOfRangeException(); } + internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) { throw CreateArgumentOutOfRangeException(argument); } [MethodImpl(MethodImplOptions.NoInlining)] - internal static Exception CreateThrowArgumentOutOfRangeException(ExceptionArgument argument) - { - return new ArgumentOutOfRangeException(argument.ToString()); - } + private static Exception CreateArgumentOutOfRangeException(ExceptionArgument argument) { return new ArgumentOutOfRangeException(argument.ToString()); } } internal enum ExceptionArgument From e4fa8ac40b2b424f40035efeb2fc5e7d24be463a Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Mon, 14 Nov 2016 09:53:23 -0800 Subject: [PATCH 20/26] Update ThrowHelper comments based on Ben's feedback. --- src/System.Memory/src/System/ThrowHelper.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/System.Memory/src/System/ThrowHelper.cs b/src/System.Memory/src/System/ThrowHelper.cs index 0ec4f30927c0..8f9daebcb255 100644 --- a/src/System.Memory/src/System/ThrowHelper.cs +++ b/src/System.Memory/src/System/ThrowHelper.cs @@ -7,11 +7,20 @@ namespace System { // - // This pattern of inlinable "void Throw" routines that stack on top of NoInlining factory methods is a compromise between older JITs and newer JITs. - // The package this file appears in is targeted specifically at older JITs. Older JITs cannot inline methods that have conditional throws in them, - // thus we use these helpers to hide the throws. Newer JIT's will inline the Throw routines and will benefit from the code size reductions of - // splitting the creation details into their own non-inlinable factory methods. + // This pattern of easily inlinable "void Throw" routines that stack on top of NoInlining factory methods + // is a compromise between older JITs and newer JITs (RyuJIT in Core CLR 1.1.0+ and desktop CLR in 4.6.3+). + // This package is explictly targeted at older JITs as newer runtimes expect to implement Span intrinsically for + // best performance. // + // The aim of this pattern is three-fold + // 1. Extracting the throw makes the method preforming the throw in a conditional branch smaller and more inlinable + // 2. Extracting the throw from generic method to non-generic method reduces the repeated codegen size for value types + // 3a. Newer JITs will not inline the methods that only throw and also recognise them, move the call to cold section + // and not add stack prep and unwind before calling https://github.com/dotnet/coreclr/pull/6103 + // 3b. Older JITs will inline the throw itself and move to cold section; but not inline the non-inlinable exception + // factory methods - still maintaining advantages 1 & 2 + // + internal static class ThrowHelper { internal static void ThrowArgumentNullException(ExceptionArgument argument) { throw CreateArgumentNullException(argument); } @@ -46,4 +55,4 @@ internal enum ExceptionArgument start, obj, } -} \ No newline at end of file +} From 4cfbba9ba3f3652bfc977ff5a317361e2160d6fd Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Mon, 14 Nov 2016 10:13:01 -0800 Subject: [PATCH 21/26] Remove trailing whitespace. --- src/System.Memory/src/System/Span.cs | 16 ++++++++-------- src/System.Memory/src/System/SpanHelpers.cs | 8 ++++---- src/System.Memory/src/System/ThrowHelper.cs | 8 ++++---- src/System.Memory/tests/Span/CtorArray.cs | 2 +- src/System.Memory/tests/Span/CtorArrayInt.cs | 4 ++-- src/System.Memory/tests/Span/CtorArrayIntInt.cs | 2 +- src/System.Memory/tests/Span/Overflow.cs | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/System.Memory/src/System/Span.cs b/src/System.Memory/src/System/Span.cs index 9e6810012357..41ba8e03fcca 100644 --- a/src/System.Memory/src/System/Span.cs +++ b/src/System.Memory/src/System/Span.cs @@ -7,7 +7,7 @@ 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)' +#pragma warning disable 0809 //warning CS0809: Obsolete member 'Span.Equals(object)' overrides non-obsolete member 'object.Equals(object)' namespace System { @@ -123,7 +123,7 @@ public unsafe Span(void* pointer, int 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 + /// if part of a managed object represents a "fixed array." This is dangerous because /// "length" is not checked, nor is the fact that "rawPointer" actually lies within the object. /// /// The managed object that contains the data to span over. @@ -165,7 +165,7 @@ private Span(Pinnable pinnable, IntPtr byteOffset, int length) public int Length => _length; /// - /// Returns true if Length is 0. + /// Returns true if Length is 0. /// public bool IsEmpty => _length == 0; @@ -194,9 +194,9 @@ public ref T this[int index] /// /// Copies the contents of this span into destination span. If the source - /// and destinations overlap, this method behaves as if the original values in + /// 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. @@ -211,9 +211,9 @@ public void CopyTo(Span destination) /// /// Copies the contents of this span into destination span. If the source - /// and destinations overlap, this method behaves as if the original values in + /// 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. /// @@ -384,7 +384,7 @@ public ref T DangerousGetPinnableReference() // _pinnable = null // _byteOffset = the pointer // - private readonly Pinnable _pinnable; + private readonly Pinnable _pinnable; private readonly IntPtr _byteOffset; private readonly int _length; } diff --git a/src/System.Memory/src/System/SpanHelpers.cs b/src/System.Memory/src/System/SpanHelpers.cs index 96a5adbccfe7..fc9c5a28cb4a 100644 --- a/src/System.Memory/src/System/SpanHelpers.cs +++ b/src/System.Memory/src/System/SpanHelpers.cs @@ -9,17 +9,17 @@ namespace System { - internal static class SpanHelpers + internal static class SpanHelpers { /// /// Computes "start + index * sizeof(T)", using the unsigned IntPtr-sized multiplication for 32 and 64 bits. - /// + /// /// Assumptions: /// Start and index are non-negative, and already pre-validated to be within the valid range of their containing Span. - /// + /// /// If the byte length (Span.Length * sizeof(T)) does an unsigned overflow (i.e. the buffer wraps or is too big to fit within the address space), /// the behavior is undefined. - /// + /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IntPtr Add(this IntPtr start, int index) diff --git a/src/System.Memory/src/System/ThrowHelper.cs b/src/System.Memory/src/System/ThrowHelper.cs index 8f9daebcb255..c44556f158e0 100644 --- a/src/System.Memory/src/System/ThrowHelper.cs +++ b/src/System.Memory/src/System/ThrowHelper.cs @@ -7,7 +7,7 @@ namespace System { // - // This pattern of easily inlinable "void Throw" routines that stack on top of NoInlining factory methods + // This pattern of easily inlinable "void Throw" routines that stack on top of NoInlining factory methods // is a compromise between older JITs and newer JITs (RyuJIT in Core CLR 1.1.0+ and desktop CLR in 4.6.3+). // This package is explictly targeted at older JITs as newer runtimes expect to implement Span intrinsically for // best performance. @@ -15,12 +15,12 @@ namespace System // The aim of this pattern is three-fold // 1. Extracting the throw makes the method preforming the throw in a conditional branch smaller and more inlinable // 2. Extracting the throw from generic method to non-generic method reduces the repeated codegen size for value types - // 3a. Newer JITs will not inline the methods that only throw and also recognise them, move the call to cold section + // 3a. Newer JITs will not inline the methods that only throw and also recognise them, move the call to cold section // and not add stack prep and unwind before calling https://github.com/dotnet/coreclr/pull/6103 - // 3b. Older JITs will inline the throw itself and move to cold section; but not inline the non-inlinable exception + // 3b. Older JITs will inline the throw itself and move to cold section; but not inline the non-inlinable exception // factory methods - still maintaining advantages 1 & 2 // - + internal static class ThrowHelper { internal static void ThrowArgumentNullException(ExceptionArgument argument) { throw CreateArgumentNullException(argument); } diff --git a/src/System.Memory/tests/Span/CtorArray.cs b/src/System.Memory/tests/Span/CtorArray.cs index 0953e4a6a5e7..9286dfa7a0e2 100644 --- a/src/System.Memory/tests/Span/CtorArray.cs +++ b/src/System.Memory/tests/Span/CtorArray.cs @@ -117,5 +117,5 @@ public static void CtorArrayWrongValueType() span.Validate(42, -1); } } -} +} diff --git a/src/System.Memory/tests/Span/CtorArrayInt.cs b/src/System.Memory/tests/Span/CtorArrayInt.cs index ec229bf18074..735dd5a00aa0 100644 --- a/src/System.Memory/tests/Span/CtorArrayInt.cs +++ b/src/System.Memory/tests/Span/CtorArrayInt.cs @@ -40,7 +40,7 @@ public static void CtorArrayIntStartTooLarge() int[] a = new int[3]; Assert.Throws(() => new Span(a, 4)); } - + [Fact] public static void CtorArrayIntStartEqualsLength() { @@ -50,5 +50,5 @@ public static void CtorArrayIntStartEqualsLength() span.Validate(); } } -} +} diff --git a/src/System.Memory/tests/Span/CtorArrayIntInt.cs b/src/System.Memory/tests/Span/CtorArrayIntInt.cs index 4a9c27184c95..c14d4523e3b1 100644 --- a/src/System.Memory/tests/Span/CtorArrayIntInt.cs +++ b/src/System.Memory/tests/Span/CtorArrayIntInt.cs @@ -76,5 +76,5 @@ public static void CtorArrayIntIntStartEqualsLength() span.Validate(); } } -} +} diff --git a/src/System.Memory/tests/Span/Overflow.cs b/src/System.Memory/tests/Span/Overflow.cs index 771a315607cf..07dc92bdf6cd 100644 --- a/src/System.Memory/tests/Span/Overflow.cs +++ b/src/System.Memory/tests/Span/Overflow.cs @@ -52,7 +52,7 @@ public static void IndexOverflow() } } } - + private const long ThreeGiB = 3L * 1024L * 1024L * 1024L; private const long TwoGiB = 2L * 1024L * 1024L * 1024L; private const long OneGiB = 1L * 1024L * 1024L * 1024L; From bdbaf5958b0fc5b59f8aca59a9f484d983fab99c Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Mon, 14 Nov 2016 13:26:19 -0800 Subject: [PATCH 22/26] Dependency bump. Again... --- src/System.Memory/src/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Memory/src/project.json b/src/System.Memory/src/project.json index a72d46e386ae..5434b991c8c6 100644 --- a/src/System.Memory/src/project.json +++ b/src/System.Memory/src/project.json @@ -5,7 +5,7 @@ "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Reflection": "4.1.0", - "System.Runtime.CompilerServices.Unsafe": "4.4.0-beta-24711-02" + "System.Runtime.CompilerServices.Unsafe": "4.4.0-beta-24714-01" }, "frameworks": { "netstandard1.0": {} From 2850306ff7fdd7233140e5e2b87b650edee85ec5 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Mon, 14 Nov 2016 15:09:01 -0800 Subject: [PATCH 23/26] Fix up some tests so the IL is Span-legal. Ah, the fun of writing Span code w/out stack-only enforcement. --- src/System.Memory/tests/Span/CopyTo.cs | 2 +- src/System.Memory/tests/Span/CtorArray.cs | 12 ++++++------ src/System.Memory/tests/Span/CtorArrayInt.cs | 4 ++-- .../tests/Span/CtorArrayIntInt.cs | 16 ++++++++-------- .../tests/Span/CtorPointerInt.cs | 4 ++-- src/System.Memory/tests/Span/Slice.cs | 14 +++++++------- src/System.Memory/tests/Span/TestHelpers.cs | 19 +++++++++++++++++++ 7 files changed, 45 insertions(+), 26 deletions(-) diff --git a/src/System.Memory/tests/Span/CopyTo.cs b/src/System.Memory/tests/Span/CopyTo.cs index 9993493a6130..9c895cbdf3b8 100644 --- a/src/System.Memory/tests/Span/CopyTo.cs +++ b/src/System.Memory/tests/Span/CopyTo.cs @@ -53,7 +53,7 @@ public static void CopyToShorter() int[] dst = { 99, 100 }; Span srcSpan = new Span(src); - Assert.Throws(() => srcSpan.CopyTo(dst)); + AssertThrows(srcSpan, (_srcSpan) => _srcSpan.CopyTo(dst)); int[] expected = { 99, 100 }; Assert.Equal(expected, dst); // CopyTo() checks for sufficient space before doing any copying. } diff --git a/src/System.Memory/tests/Span/CtorArray.cs b/src/System.Memory/tests/Span/CtorArray.cs index 9286dfa7a0e2..625dc62b81ac 100644 --- a/src/System.Memory/tests/Span/CtorArray.cs +++ b/src/System.Memory/tests/Span/CtorArray.cs @@ -83,9 +83,9 @@ public static void CtorArrayZeroLength() [Fact] public static void CtorArrayNullArray() { - Assert.Throws(() => new Span((int[])null)); - Assert.Throws(() => new Span((int[])null, 0)); - Assert.Throws(() => new Span((int[])null, 0, 0)); + Assert.Throws(() => new Span((int[])null).DontBox()); + Assert.Throws(() => new Span((int[])null, 0).DontBox()); + Assert.Throws(() => new Span((int[])null, 0, 0).DontBox()); } [Fact] @@ -93,9 +93,9 @@ public static void CtorArrayWrongArrayType() { // Cannot pass variant array, if array type is not a valuetype. string[] a = { "Hello" }; - Assert.Throws(() => new Span(a)); - Assert.Throws(() => new Span(a, 0)); - Assert.Throws(() => new Span(a, 0, a.Length)); + Assert.Throws(() => new Span(a).DontBox()); + Assert.Throws(() => new Span(a, 0).DontBox()); + Assert.Throws(() => new Span(a, 0, a.Length).DontBox()); } [Fact] diff --git a/src/System.Memory/tests/Span/CtorArrayInt.cs b/src/System.Memory/tests/Span/CtorArrayInt.cs index 735dd5a00aa0..e838d6268b75 100644 --- a/src/System.Memory/tests/Span/CtorArrayInt.cs +++ b/src/System.Memory/tests/Span/CtorArrayInt.cs @@ -31,14 +31,14 @@ public static void CtorArrayInt2() public static void CtorArrayIntNegativeStart() { int[] a = new int[3]; - Assert.Throws(() => new Span(a, -1)); + Assert.Throws(() => new Span(a, -1).DontBox()); } [Fact] public static void CtorArrayIntStartTooLarge() { int[] a = new int[3]; - Assert.Throws(() => new Span(a, 4)); + Assert.Throws(() => new Span(a, 4).DontBox()); } [Fact] diff --git a/src/System.Memory/tests/Span/CtorArrayIntInt.cs b/src/System.Memory/tests/Span/CtorArrayIntInt.cs index c14d4523e3b1..5618db5fbf19 100644 --- a/src/System.Memory/tests/Span/CtorArrayIntInt.cs +++ b/src/System.Memory/tests/Span/CtorArrayIntInt.cs @@ -39,32 +39,32 @@ public static void CtorArrayIntIntRangeExtendsToEndOfArray() public static void CtorArrayIntIntNegativeStart() { int[] a = new int[3]; - Assert.Throws(() => new Span(a, -1, 0)); + Assert.Throws(() => new Span(a, -1, 0).DontBox()); } [Fact] public static void CtorArrayIntIntStartTooLarge() { int[] a = new int[3]; - Assert.Throws(() => new Span(a, 4, 0)); + Assert.Throws(() => new Span(a, 4, 0).DontBox()); } [Fact] public static void CtorArrayIntIntNegativeLength() { int[] a = new int[3]; - Assert.Throws(() => new Span(a, 0, -1)); + Assert.Throws(() => new Span(a, 0, -1).DontBox()); } [Fact] public static void CtorArrayIntIntStartAndLengthTooLarge() { int[] a = new int[3]; - Assert.Throws(() => new Span(a, 3, 1)); - Assert.Throws(() => new Span(a, 2, 2)); - Assert.Throws(() => new Span(a, 1, 3)); - Assert.Throws(() => new Span(a, 0, 4)); - Assert.Throws(() => new Span(a, int.MaxValue, int.MaxValue)); + Assert.Throws(() => new Span(a, 3, 1).DontBox()); + Assert.Throws(() => new Span(a, 2, 2).DontBox()); + Assert.Throws(() => new Span(a, 1, 3).DontBox()); + Assert.Throws(() => new Span(a, 0, 4).DontBox()); + Assert.Throws(() => new Span(a, int.MaxValue, int.MaxValue).DontBox()); } [Fact] diff --git a/src/System.Memory/tests/Span/CtorPointerInt.cs b/src/System.Memory/tests/Span/CtorPointerInt.cs index 289b882c160b..a06a75c12924 100644 --- a/src/System.Memory/tests/Span/CtorPointerInt.cs +++ b/src/System.Memory/tests/Span/CtorPointerInt.cs @@ -58,8 +58,8 @@ public static void CtorPointerNoContainsReferenceEnforcement() { new Span((void*)null, 0); new Span((void*)null, 0); - Assert.Throws(() => new Span((void*)null, 0)); - Assert.Throws(() => new Span((void*)null, 0)); + Assert.Throws(() => new Span((void*)null, 0).DontBox()); + Assert.Throws(() => new Span((void*)null, 0).DontBox()); } } diff --git a/src/System.Memory/tests/Span/Slice.cs b/src/System.Memory/tests/Span/Slice.cs index 93a7217938bd..31fa06ed91b3 100644 --- a/src/System.Memory/tests/Span/Slice.cs +++ b/src/System.Memory/tests/Span/Slice.cs @@ -58,13 +58,13 @@ public static void SliceIntIntPastEnd() public static void SliceIntRangeChecksd() { int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }; - Assert.Throws(() => new Span(a).Slice(-1)); - Assert.Throws(() => new Span(a).Slice(a.Length + 1)); - Assert.Throws(() => new Span(a).Slice(-1, 0)); - Assert.Throws(() => new Span(a).Slice(0, a.Length + 1)); - Assert.Throws(() => new Span(a).Slice(2, a.Length + 1 - 2)); - Assert.Throws(() => new Span(a).Slice(a.Length + 1, 0)); - Assert.Throws(() => new Span(a).Slice(a.Length, 1)); + Assert.Throws(() => new Span(a).Slice(-1).DontBox()); + Assert.Throws(() => new Span(a).Slice(a.Length + 1).DontBox()); + Assert.Throws(() => new Span(a).Slice(-1, 0).DontBox()); + Assert.Throws(() => new Span(a).Slice(0, a.Length + 1).DontBox()); + Assert.Throws(() => new Span(a).Slice(2, a.Length + 1 - 2).DontBox()); + Assert.Throws(() => new Span(a).Slice(a.Length + 1, 0).DontBox()); + Assert.Throws(() => new Span(a).Slice(a.Length, 1).DontBox()); } } } diff --git a/src/System.Memory/tests/Span/TestHelpers.cs b/src/System.Memory/tests/Span/TestHelpers.cs index b90227aa9fe5..0837eeab9a95 100644 --- a/src/System.Memory/tests/Span/TestHelpers.cs +++ b/src/System.Memory/tests/Span/TestHelpers.cs @@ -45,6 +45,25 @@ private static void AssertThrows(Span span, AssertThrowsAction actio } } + // + // The innocent looking construct: + // + // Assert.Throws( () => new Span() ); + // + // generates a hidden box of the Span as the return value of the lambda. This makes the IL illegal and unloadable on + // runtimes that enforce the actual Span rules (never mind that we expect never to reach the box instruction...) + // + // The workaround is to code it like this: + // + // Assert.Throws( () => new Span().DontBox() ); + // + // which turns the lambda return type back to "void" and eliminates the troublesome box instruction. + // + private static void DontBox(this Span span) + { + // This space intentionally left blank. + } + [StructLayout(LayoutKind.Sequential)] private sealed class TestClass { From a65d0b4a2843ea278ea6715bc0c576eff39b7fe7 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Mon, 14 Nov 2016 15:30:33 -0800 Subject: [PATCH 24/26] Add a reference assembly and some of the plumbing... ...to typeforward on CoreClr. Right now, CoreClr fails 44 out of 55 tests so I believe it's premature to throw the switch just yet. This just gets some of the grunt work out of the way. --- src/System.Memory/ref/System.Memory.builds | 8 +++++ src/System.Memory/ref/System.Memory.cs | 34 ++++++++++++++++++++++ src/System.Memory/ref/System.Memory.csproj | 19 ++++++++++++ src/System.Memory/ref/project.json | 9 ++++++ src/System.Memory/src/System.Memory.csproj | 6 +++- 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 src/System.Memory/ref/System.Memory.builds create mode 100644 src/System.Memory/ref/System.Memory.cs create mode 100644 src/System.Memory/ref/System.Memory.csproj create mode 100644 src/System.Memory/ref/project.json diff --git a/src/System.Memory/ref/System.Memory.builds b/src/System.Memory/ref/System.Memory.builds new file mode 100644 index 000000000000..87b2f7a0539d --- /dev/null +++ b/src/System.Memory/ref/System.Memory.builds @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs new file mode 100644 index 000000000000..33015b0c6475 --- /dev/null +++ b/src/System.Memory/ref/System.Memory.cs @@ -0,0 +1,34 @@ +#pragma warning disable 0809 //warning CS0809: Obsolete member 'Span.Equals(object)' overrides non-obsolete member 'object.Equals(object)' +namespace System +{ + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct Span + { + public static readonly System.Span Empty; + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public Span(T[] array) { throw null;} + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public Span(T[] array, int start) { throw null;} + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public Span(T[] array, int start, int length) { throw null;} + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public unsafe Span(void* pointer, int length) { throw null;} + public bool IsEmpty { get { throw null; } } + public ref T this[int index] { [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]get { throw null; } } + public int Length { get { throw null; } } + public void CopyTo(System.Span destination) { } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static System.Span DangerousCreate(object obj, ref T objectData, int length) { throw null; } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public ref T DangerousGetPinnableReference() { throw null; } + [System.ObsoleteAttribute("Equals() on Span will always throw an exception. Use == instead.")] + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public override bool Equals(object obj) { throw null; } + [System.ObsoleteAttribute("GetHashCode() on Span will always throw an exception.")] + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public override int GetHashCode() { throw null; } + public static bool operator ==(System.Span left, System.Span right) { throw null; } + public static implicit operator System.Span (T[] array) { throw null; } + public static implicit operator System.Span (System.ArraySegment arraySegment) { throw null; } + public static bool operator !=(System.Span left, System.Span right) { throw null; } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public System.Span Slice(int start) { throw null; } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public System.Span Slice(int start, int length) { throw null; } + public T[] ToArray() { throw null; } + public bool TryCopyTo(System.Span destination) { throw null; } + } +} + diff --git a/src/System.Memory/ref/System.Memory.csproj b/src/System.Memory/ref/System.Memory.csproj new file mode 100644 index 000000000000..f1256bbb3206 --- /dev/null +++ b/src/System.Memory/ref/System.Memory.csproj @@ -0,0 +1,19 @@ + + + + + true + Library + false + 4.0.0.0 + .NETStandard,Version=v1.0 + {0EF9D369-7097-44F9-BEBA-C32AF5EB4756} + + + + + + + + + diff --git a/src/System.Memory/ref/project.json b/src/System.Memory/ref/project.json new file mode 100644 index 000000000000..9bda63161248 --- /dev/null +++ b/src/System.Memory/ref/project.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Runtime": "4.1.0" + }, + "frameworks": { + "netstandard1.0": {} + } +} diff --git a/src/System.Memory/src/System.Memory.csproj b/src/System.Memory/src/System.Memory.csproj index a53e3efed34b..c0ddd27cf6e2 100644 --- a/src/System.Memory/src/System.Memory.csproj +++ b/src/System.Memory/src/System.Memory.csproj @@ -9,15 +9,19 @@ false .NETStandard,Version=v1.0 $(OutputPath)$(AssemblyName).xml + false - + + + + From da8894d6b80cd0bf26732ea7121adf7bd2b747c9 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Tue, 15 Nov 2016 11:54:49 -0800 Subject: [PATCH 25/26] Add C#6.0 workaround. After chatting offline, we'll accomodate C# 6.0 for now by having the indexer return "T" rather than "ref T". Some performant code require the "ref T" version so as another stopgap, we'll provide a GetItem() method that returns "ref T". Once the tooling story is in better shape, we'll merge GetItem() and the indexer into a single indexer that returns "ref T". --- src/System.Memory/ref/System.Memory.cs | 3 +- src/System.Memory/src/System/Span.cs | 46 +++++++++++++++++-- .../tests/Span/CtorPointerInt.cs | 2 +- .../tests/Span/DangerousCreate.cs | 2 +- src/System.Memory/tests/Span/Overflow.cs | 6 +-- src/System.Memory/tests/Span/Slice.cs | 6 +-- 6 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index 33015b0c6475..5716a32bd98d 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -10,7 +10,8 @@ public partial struct Span [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public Span(T[] array, int start, int length) { throw null;} [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public unsafe Span(void* pointer, int length) { throw null;} public bool IsEmpty { get { throw null; } } - public ref T this[int index] { [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]get { throw null; } } + public T this[int index] { [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]get { throw null; } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]set { throw null; }} + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public ref T GetItem(int index) { throw null; } public int Length { get { throw null; } } public void CopyTo(System.Span destination) { } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static System.Span DangerousCreate(object obj, ref T objectData, int length) { throw null; } diff --git a/src/System.Memory/src/System/Span.cs b/src/System.Memory/src/System/Span.cs index 41ba8e03fcca..cf7a059216f4 100644 --- a/src/System.Memory/src/System/Span.cs +++ b/src/System.Memory/src/System/Span.cs @@ -177,7 +177,11 @@ private Span(Pinnable pinnable, IntPtr byteOffset, int length) /// /// Thrown when index less than 0 or index greater than or equal to Length /// - public ref T this[int index] + + // TODO: https://github.com/dotnet/corefx/issues/13681 + // Until we get over the hurdle of C# 7 tooling, this indexer will return "T" and have a setter rather than a "ref T". (The doc comments + // continue to reflect the original intent of returning "ref T") + public T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -186,10 +190,46 @@ public ref T this[int index] ThrowHelper.ThrowIndexOutOfRangeException(); if (_pinnable == null) - unsafe { return ref Unsafe.Add(ref Unsafe.AsRef(_byteOffset.ToPointer()), index); } + unsafe { return Unsafe.Add(ref Unsafe.AsRef(_byteOffset.ToPointer()), index); } else - return ref Unsafe.Add(ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset), index); + return Unsafe.Add(ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset), index); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + if ((uint) index >= ((uint) _length)) + ThrowHelper.ThrowIndexOutOfRangeException(); + + if (_pinnable == null) + unsafe { Unsafe.Add(ref Unsafe.AsRef(_byteOffset.ToPointer()), index) = value; } + else + Unsafe.Add(ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset), index) = value; + } + } + + /// + /// Returns a reference to specified element of the Span. + /// + /// + /// + /// + /// Thrown when index less than 0 or index greater than or equal to Length + /// + + // TODO: https://github.com/dotnet/corefx/issues/13681 + // Until we get over the hurdle of C# 7 tooling, this temporary method will simulate the intended "ref T" indexer for those + // who need bypass the workaround for performance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T GetItem(int index) + { + if ((uint)index >= ((uint)_length)) + ThrowHelper.ThrowIndexOutOfRangeException(); + + if (_pinnable == null) + unsafe { return ref Unsafe.Add(ref Unsafe.AsRef(_byteOffset.ToPointer()), index); } + else + return ref Unsafe.Add(ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset), index); } /// diff --git a/src/System.Memory/tests/Span/CtorPointerInt.cs b/src/System.Memory/tests/Span/CtorPointerInt.cs index a06a75c12924..6bb247600c9e 100644 --- a/src/System.Memory/tests/Span/CtorPointerInt.cs +++ b/src/System.Memory/tests/Span/CtorPointerInt.cs @@ -21,7 +21,7 @@ public static void CtorPointerInt() { Span span = new Span(pa, 3); span.Validate(90, 91, 92); - Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(pa), ref span[0])); + Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(pa), ref span.DangerousGetPinnableReference())); } } } diff --git a/src/System.Memory/tests/Span/DangerousCreate.cs b/src/System.Memory/tests/Span/DangerousCreate.cs index 8188ee5052ae..55eaf075bc99 100644 --- a/src/System.Memory/tests/Span/DangerousCreate.cs +++ b/src/System.Memory/tests/Span/DangerousCreate.cs @@ -44,7 +44,7 @@ public static void DangerousCreate1() Span span = Span.DangerousCreate(testClass, ref testClass.C1, 3); span.Validate('b', 'c', 'd'); - ref char pc1 = ref span[0]; + ref char pc1 = ref span.DangerousGetPinnableReference(); Assert.True(Unsafe.AreSame(ref testClass.C1, ref pc1)); } } diff --git a/src/System.Memory/tests/Span/Overflow.cs b/src/System.Memory/tests/Span/Overflow.cs index 07dc92bdf6cd..228ee75bca6b 100644 --- a/src/System.Memory/tests/Span/Overflow.cs +++ b/src/System.Memory/tests/Span/Overflow.cs @@ -38,13 +38,13 @@ public static void IndexOverflow() Assert.True(byteOffset > (uint)int.MaxValue); // Make sure byteOffset actually overflows 2Gb, or this test is pointless. ref Guid expected = ref Unsafe.AsRef(((byte*)pMemory) + byteOffset); - Assert.True(Unsafe.AreSame(ref expected, ref span[bigIndex])); + Assert.True(Unsafe.AreSame(ref expected, ref span.GetItem(bigIndex))); Span slice = span.Slice(bigIndex); - Assert.True(Unsafe.AreSame(ref expected, ref slice[0])); + Assert.True(Unsafe.AreSame(ref expected, ref slice.DangerousGetPinnableReference())); slice = span.Slice(bigIndex, 1); - Assert.True(Unsafe.AreSame(ref expected, ref slice[0])); + Assert.True(Unsafe.AreSame(ref expected, ref slice.DangerousGetPinnableReference())); } finally { diff --git a/src/System.Memory/tests/Span/Slice.cs b/src/System.Memory/tests/Span/Slice.cs index 31fa06ed91b3..e15f4cb2bbd0 100644 --- a/src/System.Memory/tests/Span/Slice.cs +++ b/src/System.Memory/tests/Span/Slice.cs @@ -15,7 +15,7 @@ public static void SliceInt() int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }; Span span = new Span(a).Slice(6); Assert.Equal(4, span.Length); - Assert.True(Unsafe.AreSame(ref a[6], ref span[0])); + Assert.True(Unsafe.AreSame(ref a[6], ref span.DangerousGetPinnableReference())); } [Fact] @@ -33,7 +33,7 @@ public static void SliceIntInt() int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }; Span span = new Span(a).Slice(3, 5); Assert.Equal(5, span.Length); - Assert.True(Unsafe.AreSame(ref a[3], ref span[0])); + Assert.True(Unsafe.AreSame(ref a[3], ref span.DangerousGetPinnableReference())); } [Fact] @@ -42,7 +42,7 @@ public static void SliceIntIntUpToEnd() int[] a = { 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }; Span span = new Span(a).Slice(4, 6); Assert.Equal(6, span.Length); - Assert.True(Unsafe.AreSame(ref a[4], ref span[0])); + Assert.True(Unsafe.AreSame(ref a[4], ref span.DangerousGetPinnableReference())); } [Fact] From d551c71f9f79bb59a0c699968caf322945b443c5 Mon Sep 17 00:00:00 2001 From: Atsushi Kanamori Date: Tue, 15 Nov 2016 12:00:46 -0800 Subject: [PATCH 26/26] Dependency update again. This is getting really monotonous. --- src/System.Memory/src/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Memory/src/project.json b/src/System.Memory/src/project.json index 5434b991c8c6..e4874791d63d 100644 --- a/src/System.Memory/src/project.json +++ b/src/System.Memory/src/project.json @@ -5,7 +5,7 @@ "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Reflection": "4.1.0", - "System.Runtime.CompilerServices.Unsafe": "4.4.0-beta-24714-01" + "System.Runtime.CompilerServices.Unsafe": "4.4.0-beta-24715-02" }, "frameworks": { "netstandard1.0": {}