Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/corert.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Common/src/TypeSystem/IL/DelegateInfo.cs5
-rw-r--r--src/Common/src/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.Sorting.cs20
-rw-r--r--src/Common/src/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.cs145
-rw-r--r--src/Common/src/TypeSystem/IL/TypeSystemContext.ValueTypeMethods.cs243
-rw-r--r--src/ILCompiler.Compiler/src/Compiler/CompilerTypeSystemContext.cs41
-rw-r--r--src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/MrtImports.cs4
-rw-r--r--src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/RuntimeDecodableJumpStub.cs6
-rw-r--r--src/ILCompiler.Compiler/src/Compiler/PrecomputedMetadataManager.cs88
-rw-r--r--src/ILCompiler.TypeSystem/src/ILCompiler.TypeSystem.csproj9
-rw-r--r--src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs57
-rw-r--r--src/ILVerification/StrongNameKeys/ILVerify.snkbin0 -> 596 bytes
-rw-r--r--src/ILVerification/src/AssemblyInfo.cs4
-rw-r--r--src/ILVerification/src/ILVerification.csproj20
-rw-r--r--src/ILVerification/src/Verifier.cs2
-rw-r--r--src/ILVerification/tests/ILVerification.Tests.csproj11
-rw-r--r--src/ILVerify/netcoreapp/ILVerify.cs9
-rw-r--r--src/ILVerify/netcoreapp/ILVerify.csproj14
-rw-r--r--src/ILVerify/src/AssemblyInfo.cs3
-rw-r--r--src/ILVerify/src/ILVerify.csproj50
-rw-r--r--src/ILVerify/src/ILVerify.runtimeconfig.json7
-rw-r--r--src/ILVerify/src/Program.cs5
-rw-r--r--src/JitInterface/src/CorInfoImpl.cs1
-rw-r--r--src/Native/Runtime/AsmOffsetsVerify.cpp4
-rw-r--r--src/Native/Runtime/CachedInterfaceDispatch.cpp2
-rw-r--r--src/Native/Runtime/RHCodeMan.cpp2
-rw-r--r--src/Native/Runtime/RhConfig.cpp8
-rw-r--r--src/Native/Runtime/RhConfig.h4
-rw-r--r--src/Native/Runtime/StackFrameIterator.cpp4
-rw-r--r--src/Native/Runtime/coreclr/gcinfodecoder.cpp8
-rw-r--r--src/Native/Runtime/module.cpp2
-rw-r--r--src/Native/Runtime/thread.cpp2
-rw-r--r--src/Native/Runtime/windows/CoffNativeCodeManager.cpp2
-rw-r--r--src/Native/gc/gc.cpp26
-rw-r--r--src/Native/gc/handletable.cpp2
-rw-r--r--src/Native/gc/handletablecore.cpp18
-rw-r--r--src/Native/gc/handletablescan.cpp8
-rw-r--r--src/Native/gc/objecthandle.cpp2
-rw-r--r--src/Native/gc/unix/gcenv.unix.cpp2
-rw-r--r--src/Native/libunwind/src/Unwind-EHABI.cpp2
-rw-r--r--src/Native/libunwind/src/Unwind_AppleExtras.cpp2
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs4
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFullPathNameW.cs2
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetLongPathNameW.cs2
-rw-r--r--src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems10
-rw-r--r--src/System.Private.CoreLib/shared/System/Boolean.cs10
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/ValueListBuilder.cs2
-rw-r--r--src/System.Private.CoreLib/shared/System/Convert.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Debug.Unix.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Debug.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs20
-rw-r--r--src/System.Private.CoreLib/shared/System/Double.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Invariant.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs40
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/DateTimeParse.cs4
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.Win32.cs2
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.Win32.cs2
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs6
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs86
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Path.cs244
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs411
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.StringBuffer.cs91
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs66
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathInternal.cs8
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs4
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Win32Marshal.cs2
-rw-r--r--src/System.Private.CoreLib/shared/System/Memory.cs30
-rw-r--r--src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs540
-rw-r--r--src/System.Private.CoreLib/shared/System/MemoryExtensions.cs1170
-rw-r--r--src/System.Private.CoreLib/shared/System/Number.Formatting.cs2
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs10
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/StringBuffer.cs301
-rw-r--r--src/System.Private.CoreLib/shared/System/Single.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Span.Fast.cs4
-rw-r--r--src/System.Private.CoreLib/shared/System/Span.NonGeneric.cs865
-rw-r--r--src/System.Private.CoreLib/shared/System/SpanHelpers.BinarySearch.cs83
-rw-r--r--src/System.Private.CoreLib/shared/System/SpanHelpers.Byte.cs1104
-rw-r--r--src/System.Private.CoreLib/shared/System/SpanHelpers.T.cs683
-rw-r--r--src/System.Private.CoreLib/shared/System/SpanHelpers.cs503
-rw-r--r--src/System.Private.CoreLib/shared/System/String.Manipulation.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/StringSpanHelpers.cs19
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs2
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/ValueStringBuilder.cs39
-rw-r--r--src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs6
-rw-r--r--src/System.Private.CoreLib/shared/System/Version.cs4
-rw-r--r--src/System.Private.CoreLib/src/Resources/Strings.resx3
-rw-r--r--src/System.Private.CoreLib/src/System/Globalization/CompareInfo.Unix.cs334
-rw-r--r--src/System.Private.CoreLib/src/System/Globalization/CompareInfo.Windows.cs44
-rw-r--r--src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs2
-rw-r--r--src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs4
-rw-r--r--src/System.Private.CoreLib/src/System/String.Comparison.cs38
-rw-r--r--src/System.Private.CoreLib/src/System/Threading/ThreadPool.cs215
-rw-r--r--src/System.Private.CoreLib/src/System/ThrowHelper.cs12
-rw-r--r--src/System.Private.CoreLib/src/System/ValueType.cs163
-rw-r--r--src/System.Private.Interop/src/Internal/Runtime/CompilerHelpers/RuntimeInteropData.ProjectN.cs4
-rw-r--r--src/System.Private.Interop/src/System.Private.Interop.csproj4
-rw-r--r--src/System.Private.Reflection.Core/src/System/Reflection/Runtime/General/TypeUnifier.cs10
-rw-r--r--src/dirs.proj5
97 files changed, 6148 insertions, 2029 deletions
diff --git a/src/Common/src/TypeSystem/IL/DelegateInfo.cs b/src/Common/src/TypeSystem/IL/DelegateInfo.cs
index 66267bf45..5322fc618 100644
--- a/src/Common/src/TypeSystem/IL/DelegateInfo.cs
+++ b/src/Common/src/TypeSystem/IL/DelegateInfo.cs
@@ -209,7 +209,10 @@ namespace Internal.IL
private static bool IsNativeCallingConventionCompatible(TypeDesc type)
{
- if (type.IsPointer || type.IsByRef)
+ if (type.IsPointer)
+ return true;
+
+ if (type.IsByRef)
return IsNativeCallingConventionCompatible(((ParameterizedType)type).ParameterType);
if (!type.IsValueType)
diff --git a/src/Common/src/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.Sorting.cs b/src/Common/src/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.Sorting.cs
new file mode 100644
index 000000000..4122b8c3c
--- /dev/null
+++ b/src/Common/src/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.Sorting.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 Internal.TypeSystem;
+
+namespace Internal.IL.Stubs
+{
+ partial class ValueTypeGetFieldHelperMethodOverride
+ {
+ protected internal override int ClassCode => 2036839816;
+
+ protected internal override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer)
+ {
+ var otherMethod = (ValueTypeGetFieldHelperMethodOverride)other;
+
+ return comparer.Compare(_owningType, otherMethod._owningType);
+ }
+ }
+}
diff --git a/src/Common/src/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.cs b/src/Common/src/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.cs
new file mode 100644
index 000000000..33d71794a
--- /dev/null
+++ b/src/Common/src/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.cs
@@ -0,0 +1,145 @@
+// 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.Collections.Generic;
+
+using Internal.TypeSystem;
+
+namespace Internal.IL.Stubs
+{
+ /// <summary>
+ /// Synthetic method override of "int ValueType.__GetFieldHelper(Int32, out EETypePtr)". This method is injected
+ /// into all value types that cannot have their Equals(object) and GetHashCode() methods operate on individual
+ /// bytes. The purpose of the override is to provide access to the value types' fields and their types.
+ /// </summary>
+ public sealed partial class ValueTypeGetFieldHelperMethodOverride : ILStubMethod
+ {
+ private DefType _owningType;
+ private MethodSignature _signature;
+
+ internal ValueTypeGetFieldHelperMethodOverride(DefType owningType)
+ {
+ _owningType = owningType;
+ }
+
+ public override TypeSystemContext Context
+ {
+ get
+ {
+ return _owningType.Context;
+ }
+ }
+
+ public override TypeDesc OwningType
+ {
+ get
+ {
+ return _owningType;
+ }
+ }
+
+ public override MethodSignature Signature
+ {
+ get
+ {
+ if (_signature == null)
+ {
+ TypeSystemContext context = _owningType.Context;
+ TypeDesc int32Type = context.GetWellKnownType(WellKnownType.Int32);
+ TypeDesc eeTypePtrType = context.SystemModule.GetKnownType("System", "EETypePtr");
+
+ _signature = new MethodSignature(0, 0, int32Type, new[] {
+ int32Type,
+ eeTypePtrType.MakeByRefType()
+ });
+ }
+
+ return _signature;
+ }
+ }
+
+ public override MethodIL EmitIL()
+ {
+ TypeDesc owningType = _owningType.InstantiateAsOpen();
+
+ ILEmitter emitter = new ILEmitter();
+
+ TypeDesc eeTypePtrType = Context.SystemModule.GetKnownType("System", "EETypePtr");
+ MethodDesc eeTypePtrOfMethod = eeTypePtrType.GetKnownMethod("EETypePtrOf", null);
+ ILToken eeTypePtrToken = emitter.NewToken(eeTypePtrType);
+
+ var switchStream = emitter.NewCodeStream();
+ var getFieldStream = emitter.NewCodeStream();
+
+ ArrayBuilder<ILCodeLabel> fieldGetters = new ArrayBuilder<ILCodeLabel>();
+ foreach (FieldDesc field in owningType.GetFields())
+ {
+ if (field.IsStatic)
+ continue;
+
+ ILCodeLabel label = emitter.NewCodeLabel();
+ fieldGetters.Add(label);
+
+ getFieldStream.EmitLabel(label);
+ getFieldStream.EmitLdArg(2);
+
+ // We need something we can instantiate EETypePtrOf over. Also, the classlib
+ // code doesn't handle pointers.
+ TypeDesc boxableFieldType = field.FieldType;
+ if (boxableFieldType.IsPointer || boxableFieldType.IsFunctionPointer)
+ boxableFieldType = Context.GetWellKnownType(WellKnownType.IntPtr);
+
+ MethodDesc ptrOfField = eeTypePtrOfMethod.MakeInstantiatedMethod(boxableFieldType);
+ getFieldStream.Emit(ILOpcode.call, emitter.NewToken(ptrOfField));
+
+ getFieldStream.Emit(ILOpcode.stobj, eeTypePtrToken);
+
+ getFieldStream.EmitLdArg(0);
+ getFieldStream.Emit(ILOpcode.ldflda, emitter.NewToken(field));
+
+ getFieldStream.EmitLdArg(0);
+
+ getFieldStream.Emit(ILOpcode.sub);
+
+ getFieldStream.Emit(ILOpcode.ret);
+ }
+
+ if (fieldGetters.Count > 0)
+ {
+ switchStream.EmitLdArg(1);
+ switchStream.EmitSwitch(fieldGetters.ToArray());
+ }
+
+ switchStream.EmitLdc(fieldGetters.Count);
+
+ switchStream.Emit(ILOpcode.ret);
+
+ return emitter.Link(this);
+ }
+
+ public override Instantiation Instantiation
+ {
+ get
+ {
+ return Instantiation.Empty;
+ }
+ }
+
+ public override bool IsVirtual
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public override string Name
+ {
+ get
+ {
+ return "__GetFieldHelper";
+ }
+ }
+ }
+}
diff --git a/src/Common/src/TypeSystem/IL/TypeSystemContext.ValueTypeMethods.cs b/src/Common/src/TypeSystem/IL/TypeSystemContext.ValueTypeMethods.cs
new file mode 100644
index 000000000..a1487a5ac
--- /dev/null
+++ b/src/Common/src/TypeSystem/IL/TypeSystemContext.ValueTypeMethods.cs
@@ -0,0 +1,243 @@
+// 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.Collections.Generic;
+
+using Internal.IL.Stubs;
+
+using Debug = System.Diagnostics.Debug;
+
+namespace Internal.TypeSystem
+{
+ public abstract partial class TypeSystemContext
+ {
+ private MethodDesc _objectEqualsMethod;
+
+ private class ValueTypeMethodHashtable : LockFreeReaderHashtable<DefType, MethodDesc>
+ {
+ protected override int GetKeyHashCode(DefType key) => key.GetHashCode();
+ protected override int GetValueHashCode(MethodDesc value) => value.OwningType.GetHashCode();
+ protected override bool CompareKeyToValue(DefType key, MethodDesc value) => key == value.OwningType;
+ protected override bool CompareValueToValue(MethodDesc v1, MethodDesc v2) => v1.OwningType == v2.OwningType;
+
+ protected override MethodDesc CreateValueFromKey(DefType key)
+ {
+ return new ValueTypeGetFieldHelperMethodOverride(key);
+ }
+ }
+
+ private ValueTypeMethodHashtable _valueTypeMethodHashtable = new ValueTypeMethodHashtable();
+
+ protected virtual IEnumerable<MethodDesc> GetAllMethodsForValueType(TypeDesc valueType)
+ {
+ TypeDesc valueTypeDefinition = valueType.GetTypeDefinition();
+
+ if (RequiresGetFieldHelperMethod((MetadataType)valueTypeDefinition))
+ {
+ MethodDesc getFieldHelperMethod = _valueTypeMethodHashtable.GetOrCreateValue((DefType)valueTypeDefinition);
+
+ // Check that System.ValueType has the method we're overriding.
+ Debug.Assert(valueTypeDefinition.BaseType.GetMethod(getFieldHelperMethod.Name, null) != null);
+
+ if (valueType != valueTypeDefinition)
+ {
+ yield return GetMethodForInstantiatedType(getFieldHelperMethod, (InstantiatedType)valueType);
+ }
+ else
+ {
+ yield return getFieldHelperMethod;
+ }
+ }
+
+ foreach (MethodDesc method in valueType.GetMethods())
+ yield return method;
+ }
+
+ private bool RequiresGetFieldHelperMethod(MetadataType valueType)
+ {
+ if (_objectEqualsMethod == null)
+ _objectEqualsMethod = GetWellKnownType(WellKnownType.Object).GetMethod("Equals", null);
+
+ // If the classlib doesn't have Object.Equals, we don't need this.
+ if (_objectEqualsMethod == null)
+ return false;
+
+ // Byref-like valuetypes cannot be boxed.
+ if (valueType.IsByRefLike)
+ return false;
+
+ // Enums get their overrides from System.Enum.
+ if (valueType.IsEnum)
+ return false;
+
+ return !_typeStateHashtable.GetOrCreateValue(valueType).CanCompareValueTypeBits;
+ }
+
+ private class TypeState
+ {
+ private enum Flags
+ {
+ CanCompareValueTypeBits = 0x0000_0001,
+ CanCompareValueTypeBitsComputed = 0x0000_0002,
+ }
+
+ private volatile Flags _flags;
+ private readonly TypeStateHashtable _hashtable;
+
+ public TypeDesc Type { get; }
+
+ public bool CanCompareValueTypeBits
+ {
+ get
+ {
+ Flags flags = _flags;
+ if ((flags & Flags.CanCompareValueTypeBitsComputed) == 0)
+ {
+ Debug.Assert(Type.IsValueType);
+ if (ComputeCanCompareValueTypeBits((MetadataType)Type))
+ flags |= Flags.CanCompareValueTypeBits;
+ flags |= Flags.CanCompareValueTypeBitsComputed;
+
+ _flags = flags;
+ }
+ return (flags & Flags.CanCompareValueTypeBits) != 0;
+ }
+ }
+
+ public TypeState(TypeDesc type, TypeStateHashtable hashtable)
+ {
+ Type = type;
+ _hashtable = hashtable;
+ }
+
+ private bool ComputeCanCompareValueTypeBits(MetadataType type)
+ {
+ Debug.Assert(type.IsValueType);
+
+ if (type.ContainsGCPointers)
+ return false;
+
+ if (type.IsGenericDefinition)
+ return false;
+
+ OverlappingFieldTracker overlappingFieldTracker = new OverlappingFieldTracker(type);
+
+ bool result = true;
+ foreach (var field in type.GetFields())
+ {
+ if (field.IsStatic)
+ continue;
+
+ if (!overlappingFieldTracker.TrackField(field))
+ {
+ // This field overlaps with another field - can't compare memory
+ result = false;
+ break;
+ }
+
+ TypeDesc fieldType = field.FieldType;
+ if (fieldType.IsPrimitive || fieldType.IsEnum || fieldType.IsPointer || fieldType.IsFunctionPointer)
+ {
+ TypeFlags category = fieldType.UnderlyingType.Category;
+ if (category == TypeFlags.Single || category == TypeFlags.Double)
+ {
+ // Double/Single have weird behaviors around negative/positive zero
+ result = false;
+ break;
+ }
+ }
+ else
+ {
+ // Would be a suprise if this wasn't a valuetype. We checked ContainsGCPointers above.
+ Debug.Assert(fieldType.IsValueType);
+
+ MethodDesc objectEqualsMethod = fieldType.Context._objectEqualsMethod;
+
+ // If the field overrides Equals, we can't use the fast helper because we need to call the method.
+ if (fieldType.FindVirtualFunctionTargetMethodOnObjectType(objectEqualsMethod).OwningType == fieldType)
+ {
+ result = false;
+ break;
+ }
+
+ if (!_hashtable.GetOrCreateValue((MetadataType)fieldType).CanCompareValueTypeBits)
+ {
+ result = false;
+ break;
+ }
+ }
+ }
+
+ // If there are gaps, we can't memcompare
+ if (result && overlappingFieldTracker.HasGaps)
+ result = false;
+
+ return result;
+ }
+ }
+
+ private class TypeStateHashtable : LockFreeReaderHashtable<TypeDesc, TypeState>
+ {
+ protected override int GetKeyHashCode(TypeDesc key) => key.GetHashCode();
+ protected override int GetValueHashCode(TypeState value) => value.Type.GetHashCode();
+ protected override bool CompareKeyToValue(TypeDesc key, TypeState value) => key == value.Type;
+ protected override bool CompareValueToValue(TypeState v1, TypeState v2) => v1.Type == v2.Type;
+
+ protected override TypeState CreateValueFromKey(TypeDesc key)
+ {
+ return new TypeState(key, this);
+ }
+ }
+ private TypeStateHashtable _typeStateHashtable = new TypeStateHashtable();
+
+ private struct OverlappingFieldTracker
+ {
+ private bool[] _usedBytes;
+
+ public OverlappingFieldTracker(MetadataType type)
+ {
+ _usedBytes = new bool[type.InstanceFieldSize.AsInt];
+ }
+
+ public bool TrackField(FieldDesc field)
+ {
+ int fieldBegin = field.Offset.AsInt;
+
+ TypeDesc fieldType = field.FieldType;
+
+ int fieldEnd;
+ if (fieldType.IsPointer || fieldType.IsFunctionPointer)
+ {
+ fieldEnd = fieldBegin + field.Context.Target.PointerSize;
+ }
+ else
+ {
+ Debug.Assert(fieldType.IsValueType);
+ fieldEnd = fieldBegin + ((DefType)fieldType).InstanceFieldSize.AsInt;
+ }
+
+ for (int i = fieldBegin; i < fieldEnd; i++)
+ {
+ if (_usedBytes[i])
+ return false;
+ _usedBytes[i] = true;
+ }
+
+ return true;
+ }
+
+ public bool HasGaps
+ {
+ get
+ {
+ for (int i = 0; i < _usedBytes.Length; i++)
+ if (!_usedBytes[i])
+ return true;
+
+ return false;
+ }
+ }
+ }
+ }
+}
diff --git a/src/ILCompiler.Compiler/src/Compiler/CompilerTypeSystemContext.cs b/src/ILCompiler.Compiler/src/Compiler/CompilerTypeSystemContext.cs
index b224f558f..4feb7a582 100644
--- a/src/ILCompiler.Compiler/src/Compiler/CompilerTypeSystemContext.cs
+++ b/src/ILCompiler.Compiler/src/Compiler/CompilerTypeSystemContext.cs
@@ -97,13 +97,20 @@ namespace ILCompiler
private SimpleNameHashtable _simpleNameHashtable = new SimpleNameHashtable();
private SharedGenericsMode _genericsMode;
-
+
public CompilerTypeSystemContext(TargetDetails details, SharedGenericsMode genericsMode)
: base(details)
{
_genericsMode = genericsMode;
_vectorOfTFieldLayoutAlgorithm = new VectorOfTFieldLayoutAlgorithm(_metadataFieldLayoutAlgorithm);
+
+ GenericsConfig = new SharedGenericsConfiguration();
+ }
+
+ public SharedGenericsConfiguration GenericsConfig
+ {
+ get;
}
public IReadOnlyDictionary<string, string> InputFilePaths
@@ -345,6 +352,10 @@ namespace ILCompiler
{
return GetAllMethodsForEnum(type);
}
+ else if (type.IsValueType)
+ {
+ return GetAllMethodsForValueType(type);
+ }
return type.GetMethods();
}
@@ -473,4 +484,32 @@ namespace ILCompiler
Disabled,
CanonicalReferenceTypes,
}
+
+ public class SharedGenericsConfiguration
+ {
+ //
+ // Universal Shared Generics heuristics magic values determined empirically
+ //
+ public long UniversalCanonGVMReflectionRootHeuristic_InstantiationCount { get; }
+ public long UniversalCanonGVMDepthHeuristic_NonCanonDepth { get; }
+ public long UniversalCanonGVMDepthHeuristic_CanonDepth { get; }
+
+ // Controls how many different instantiations of a generic method, or method on generic type
+ // should be allowed before trying to fall back to only supplying USG in the reflection
+ // method table.
+ public long UniversalCanonReflectionMethodRootHeuristic_InstantiationCount { get; }
+
+ public SharedGenericsConfiguration()
+ {
+ UniversalCanonGVMReflectionRootHeuristic_InstantiationCount = 4;
+ UniversalCanonGVMDepthHeuristic_NonCanonDepth = 2;
+ UniversalCanonGVMDepthHeuristic_CanonDepth = 1;
+
+ // Unlike the GVM heuristics which are intended to kick in aggresively
+ // this heuristic exists to make it so that a fair amount of generic
+ // expansion is allowed. Numbers are chosen to allow a fairly large
+ // amount of generic expansion before trimming.
+ UniversalCanonReflectionMethodRootHeuristic_InstantiationCount = 1024;
+ }
+ };
}
diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/MrtImports.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/MrtImports.cs
index 8289c2f51..e7bce493e 100644
--- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/MrtImports.cs
+++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/MrtImports.cs
@@ -114,7 +114,7 @@ namespace ILCompiler.DependencyAnalysis
protected override sealed string GetName(NodeFactory factory)
{
- string prefix = "MrtImport " + Ordinal.ToStringInvariant() + " __mrt_";
+ string prefix = "MrtImport " + Ordinal.ToStringInvariant() + " __mrt__";
return prefix + GetNonImportedName(factory.NameMangler);
}
@@ -122,7 +122,7 @@ namespace ILCompiler.DependencyAnalysis
public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
{
- sb.Append("__mrt_").Append(nameMangler.CompilationUnitPrefix).Append(GetNonImportedName(nameMangler));
+ sb.Append("__mrt__").Append(nameMangler.CompilationUnitPrefix).Append(GetNonImportedName(nameMangler));
}
public bool RepresentsIndirectionCell => true;
diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/RuntimeDecodableJumpStub.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/RuntimeDecodableJumpStub.cs
index 1d4f00f6b..d4d8e06ff 100644
--- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/RuntimeDecodableJumpStub.cs
+++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/RuntimeDecodableJumpStub.cs
@@ -42,8 +42,10 @@ namespace ILCompiler.DependencyAnalysis
public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
{
string name = WrappedMethodIndirectionCellNode.GetMangledName(nameMangler);
- Debug.Assert(name.StartsWith("__mrt_"));
- sb.Append(name.Substring(6));
+ Debug.Assert(name.StartsWith("__mrt__"));
+ // Add a __imp__ prefix to indicate this is a stub to the debugger
+ sb.Append("__imp__");
+ sb.Append(name.Substring("__mrt__".Length));
}
protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);
diff --git a/src/ILCompiler.Compiler/src/Compiler/PrecomputedMetadataManager.cs b/src/ILCompiler.Compiler/src/Compiler/PrecomputedMetadataManager.cs
index b9d626727..0e333d323 100644
--- a/src/ILCompiler.Compiler/src/Compiler/PrecomputedMetadataManager.cs
+++ b/src/ILCompiler.Compiler/src/Compiler/PrecomputedMetadataManager.cs
@@ -144,6 +144,9 @@ namespace ILCompiler
private IEnumerable<TypeSystemEntity> ReadRequiredGenericsEntities(MethodIL method)
{
ILStreamReader il = new ILStreamReader(method);
+ bool needSecondPass = false;
+ Dictionary<MethodDesc, long> openMethodToInstantiationCount = new Dictionary<MethodDesc, long>();
+
// structure is
// REPEAT N TIMES
//ldtoken generic type/method/field
@@ -151,7 +154,7 @@ namespace ILCompiler
while (true)
{
if (il.TryReadRet()) // ret
- yield break;
+ break;
TypeSystemEntity tse;
il.TryReadLdtokenAsTypeSystemEntity(out tse);
@@ -189,12 +192,89 @@ namespace ILCompiler
genericMethod.OwningType.Instantiation.CheckValidInstantiationArguments() &&
genericMethod.CheckConstraints())
{
- // TODO: Detect large number of instantiations of the same method and collapse to using dynamic
- // USG instantiations at runtime, to avoid infinite generic expansion and large compilation times.
- yield return tse;
+ // If we encounter a large number of instantiations of the same generic method, add the universal generic form
+ // and stop adding further instantiations over the same generic method definition
+ if (genericMethod.HasInstantiation || genericMethod.OwningType.HasInstantiation)
+ {
+ MethodDesc openMethod = genericMethod.GetTypicalMethodDefinition();
+ long count;
+ if (openMethodToInstantiationCount.TryGetValue(openMethod, out count))
+ {
+ openMethodToInstantiationCount[openMethod] = count + 1;
+ }
+ else
+ {
+ openMethodToInstantiationCount.Add(openMethod, 1);
+ }
+
+ needSecondPass = true;
+ }
+ else
+ {
+ yield return tse;
+ }
}
}
}
+
+ if (needSecondPass)
+ {
+ ILStreamReader ilpass2 = new ILStreamReader(method);
+
+ while (true)
+ {
+ if (ilpass2.TryReadRet())
+ yield break;
+
+ TypeSystemEntity tse;
+ ilpass2.TryReadLdtokenAsTypeSystemEntity(out tse);
+ ilpass2.ReadPop();
+
+ if (tse == null)
+ throw new BadImageFormatException();
+
+ if (tse is MethodDesc)
+ {
+ MethodDesc genericMethod = (MethodDesc)tse;
+
+ if (genericMethod.Instantiation.CheckValidInstantiationArguments() &&
+ genericMethod.OwningType.Instantiation.CheckValidInstantiationArguments() &&
+ genericMethod.CheckConstraints())
+ {
+ // If we encounter a large number of instantiations of the same generic method, add the universal generic form
+ // and stop adding further instantiations over the same generic method definition
+ if (genericMethod.HasInstantiation || genericMethod.OwningType.HasInstantiation)
+ {
+ MethodDesc openMethod = genericMethod.GetTypicalMethodDefinition();
+ long count;
+ bool found = openMethodToInstantiationCount.TryGetValue(openMethod, out count);
+ Debug.Assert(found);
+
+ // We have 2 heuristics, one for GVMs and one for normal methods that happen to have generics
+ bool isGVM = genericMethod.IsVirtual && genericMethod.HasInstantiation;
+ long heuristicCount = isGVM ? _typeSystemContext.GenericsConfig.UniversalCanonGVMReflectionRootHeuristic_InstantiationCount :
+ _typeSystemContext.GenericsConfig.UniversalCanonReflectionMethodRootHeuristic_InstantiationCount;
+
+ if (count >= heuristicCount)
+ {
+ // We've hit the threshold of instantiations so add the USG form
+ tse = genericMethod.GetCanonMethodTarget(CanonicalFormKind.Universal);
+
+ // Set the instantiation count to -1 as a sentinel value
+ openMethodToInstantiationCount[openMethod] = -1;
+ }
+ else if (count == -1)
+ {
+ // Previously we added the USG form to _SpecifiedGenericMethods, now just skip
+ continue;
+ }
+
+ yield return tse;
+ }
+ }
+ }
+ }
+ }
}
private Instantiation GetUniversalCanonicalInstantiation(int numArgs)
diff --git a/src/ILCompiler.TypeSystem/src/ILCompiler.TypeSystem.csproj b/src/ILCompiler.TypeSystem/src/ILCompiler.TypeSystem.csproj
index d9de5b3c9..c9b2ef2bf 100644
--- a/src/ILCompiler.TypeSystem/src/ILCompiler.TypeSystem.csproj
+++ b/src/ILCompiler.TypeSystem/src/ILCompiler.TypeSystem.csproj
@@ -379,6 +379,12 @@
<Compile Include="..\..\Common\src\TypeSystem\IL\Stubs\StructMarshallingThunk.Sorting.cs">
<Link>IL\Stubs\StructMarshallingThunk.Sorting.cs</Link>
</Compile>
+ <Compile Include="..\..\Common\src\TypeSystem\IL\Stubs\ValueTypeGetFieldHelperMethodOverride.cs">
+ <Link>IL\Stubs\ValueTypeGetFieldHelperMethodOverride.cs</Link>
+ </Compile>
+ <Compile Include="..\..\Common\src\TypeSystem\IL\Stubs\ValueTypeGetFieldHelperMethodOverride.Sorting.cs">
+ <Link>IL\Stubs\ValueTypeGetFieldHelperMethodOverride.Sorting.cs</Link>
+ </Compile>
<Compile Include="..\..\Common\src\TypeSystem\IL\TypeSystemContext.DelegateInfo.cs">
<Link>IL\TypeSystemContext.DelegateInfo.cs</Link>
</Compile>
@@ -418,6 +424,9 @@
<Compile Include="..\..\Common\src\TypeSystem\IL\TypeSystemContext.EnumMethods.cs">
<Link>IL\TypeSystemContext.EnumMethods.cs</Link>
</Compile>
+ <Compile Include="..\..\Common\src\TypeSystem\IL\TypeSystemContext.ValueTypeMethods.cs">
+ <Link>IL\TypeSystemContext.ValueTypeMethods.cs</Link>
+ </Compile>
<Compile Include="..\..\Common\src\TypeSystem\Interop\IL\InlineArrayType.Sorting.cs">
<Link>TypeSystem\Interop\IL\InlineArrayType.Sorting.cs</Link>
</Compile>
diff --git a/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs b/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs
index 037140bb6..8b0936d1e 100644
--- a/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs
+++ b/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs
@@ -1062,6 +1062,63 @@ namespace Internal.IL
switch (method.Name)
{
+ case "InitializeArray":
+ if (metadataType.Namespace == "System.Runtime.CompilerServices" && metadataType.Name == "RuntimeHelpers")
+ {
+ StackEntry fieldSlot = _stack.Pop();
+ StackEntry arraySlot = _stack.Pop();
+
+ // TODO: Does fldHandle always come from ldtoken? If not, what to do with other cases?
+ if (!(fieldSlot is LdTokenEntry<FieldDesc> checkedFieldSlot) ||
+ !(_compilation.GetFieldRvaData(checkedFieldSlot.LdToken) is BlobNode fieldNode))
+ throw new InvalidProgramException("Provided field handle is invalid.");
+
+ LLVMValueRef src = LoadAddressOfSymbolNode(fieldNode);
+ _dependencies.Add(fieldNode);
+ int srcLength = fieldNode.GetData(_compilation.NodeFactory, false).Data.Length;
+
+ if (arraySlot.Type.IsSzArray)
+ {
+ // Handle single dimensional arrays (vectors).
+ LLVMValueRef arrayObjPtr = arraySlot.ValueAsType(LLVM.PointerType(LLVM.Int8Type(), 0), _builder);
+
+ var argsType = new LLVMTypeRef[]
+ {
+ LLVM.PointerType(LLVM.Int8Type(), 0),
+ LLVM.PointerType(LLVM.Int8Type(), 0),
+ LLVM.Int32Type(),
+ LLVM.Int32Type(),
+ LLVM.Int1Type()
+ };
+ LLVMValueRef memcpyFunction = GetOrCreateLLVMFunction("llvm.memcpy.p0i8.p0i8.i32", LLVM.FunctionType(LLVM.VoidType(), argsType, false));
+
+ var args = new LLVMValueRef[]
+ {
+ LLVM.BuildGEP(_builder, arrayObjPtr, new LLVMValueRef[] { ArrayBaseSize() }, string.Empty),
+ LLVM.BuildBitCast(_builder, src, LLVM.PointerType(LLVM.Int8Type(), 0), string.Empty),
+ BuildConstInt32(srcLength), // TODO: Handle destination array length to avoid runtime overflow.
+ BuildConstInt32(0), // Assume no alignment
+ BuildConstInt1(0)
+ };
+ LLVM.BuildCall(_builder, memcpyFunction, args, string.Empty);
+ }
+ else if (arraySlot.Type.IsMdArray)
+ {
+ // Handle multidimensional arrays.
+ // TODO: Add support for multidimensional array.
+ throw new NotImplementedException();
+ }
+ else
+ {
+ // Handle object-typed first argument. This include System.Array typed array, and any ill-typed argument.
+ // TODO: Emit runtime type check code on array argument and further memcpy.
+ // TODO: Maybe a new runtime interface for this is better than hand-written code emission?
+ throw new NotImplementedException();
+ }
+
+ return true;
+ }
+ break;
case "get_Value":
if (metadataType.IsByReferenceOfT)
{
diff --git a/src/ILVerification/StrongNameKeys/ILVerify.snk b/src/ILVerification/StrongNameKeys/ILVerify.snk
new file mode 100644
index 000000000..e5534f1a9
--- /dev/null
+++ b/src/ILVerification/StrongNameKeys/ILVerify.snk
Binary files differ
diff --git a/src/ILVerification/src/AssemblyInfo.cs b/src/ILVerification/src/AssemblyInfo.cs
index 722ae4299..2942fd990 100644
--- a/src/ILVerification/src/AssemblyInfo.cs
+++ b/src/ILVerification/src/AssemblyInfo.cs
@@ -1,4 +1,4 @@
using System.Runtime.CompilerServices;
-[assembly: InternalsVisibleTo("ILVerification.Tests")]
-[assembly: InternalsVisibleTo("ILVerify")]
+[assembly: InternalsVisibleTo("ILVerification.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100314e026e409db99b50d61628136c49095e67f782a3032832cfe1e61ba2af8264d2cf7d9228bdf611c1027f61b0ca4c87ee1c248cd58241a695520ba78e76d1c672c2b597cfa0ab4526dcae2b5b6f36936c126e59ada3500d656f3424826d0dab452ea407039d2846cf0e4820905eee537fe904a86097b5b2f3aaae000fc08fc3")]
+[assembly: InternalsVisibleTo("ILVerify, PublicKey=0024000004800000940000000602000000240000525341310004000001000100314e026e409db99b50d61628136c49095e67f782a3032832cfe1e61ba2af8264d2cf7d9228bdf611c1027f61b0ca4c87ee1c248cd58241a695520ba78e76d1c672c2b597cfa0ab4526dcae2b5b6f36936c126e59ada3500d656f3424826d0dab452ea407039d2846cf0e4820905eee537fe904a86097b5b2f3aaae000fc08fc3")]
diff --git a/src/ILVerification/src/ILVerification.csproj b/src/ILVerification/src/ILVerification.csproj
index 103565244..efa6e5349 100644
--- a/src/ILVerification/src/ILVerification.csproj
+++ b/src/ILVerification/src/ILVerification.csproj
@@ -1,12 +1,14 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
+<Project>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
<OutputType>Library</OutputType>
- <TargetFrameworks>netcoreapp2.0;net46</TargetFrameworks>
<PlatformTarget>AnyCPU</PlatformTarget>
<CLSCompliant>false</CLSCompliant>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
+ <TargetFramework>netstandard1.3</TargetFramework>
+ <AssemblyKey></AssemblyKey>
+ <AssemblyOriginatorKeyFile>..\StrongNameKeys\ILVerify.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
@@ -23,7 +25,6 @@
<Compile Include="VerificationResult.cs" />
<Compile Include="IResolver.cs" />
</ItemGroup>
-
<ItemGroup>
<Compile Include="..\..\Common\src\TypeSystem\CodeGen\MethodDesc.CodeGen.cs">
<Link>TypeSystem\CodeGen\MethodDesc.CodeGen.cs</Link>
@@ -285,20 +286,17 @@
</Compile>
</ItemGroup>
<ItemGroup>
- <Compile Include="..\..\Common\src\CommandLine\CommandLineException.cs">
- <Link>CommandLine\CommandLineException.cs</Link>
- </Compile>
- <Compile Include="..\..\Common\src\CommandLine\CommandLineHelpers.cs">
- <Link>CommandLine\CommandLineHelpers.cs</Link>
- </Compile>
<Compile Include="..\..\Common\src\System\NotImplemented.cs">
<Link>System\NotImplemented.cs</Link>
</Compile>
</ItemGroup>
+ <ItemGroup>
+ <None Include="..\..\ILVerification\StrongNameKeys\ILVerify.snk" Link="ILVerify.snk" />
+ </ItemGroup>
<ItemGroup>
<PackageReference Include="System.IO.MemoryMappedFiles" Version="4.3.0" />
<PackageReference Include="System.Reflection.Metadata" Version="1.4.1" />
- <PackageReference Include="System.CommandLine" Version="0.1.0-e160909-1" />
</ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
diff --git a/src/ILVerification/src/Verifier.cs b/src/ILVerification/src/Verifier.cs
index aa17d13b9..b9780d2c0 100644
--- a/src/ILVerification/src/Verifier.cs
+++ b/src/ILVerification/src/Verifier.cs
@@ -18,7 +18,7 @@ namespace ILVerify
public class Verifier
{
private Lazy<ResourceManager> _stringResourceManager =
- new Lazy<ResourceManager>(() => new ResourceManager("ILVerification.Resources.Strings", Assembly.GetExecutingAssembly()));
+ new Lazy<ResourceManager>(() => new ResourceManager("ILVerify.Resources.Strings", typeof(Verifier).GetTypeInfo().Assembly));
private ILVerifyTypeSystemContext _typeSystemContext;
diff --git a/src/ILVerification/tests/ILVerification.Tests.csproj b/src/ILVerification/tests/ILVerification.Tests.csproj
index ceca7fc93..297f5180d 100644
--- a/src/ILVerification/tests/ILVerification.Tests.csproj
+++ b/src/ILVerification/tests/ILVerification.Tests.csproj
@@ -1,9 +1,12 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
- <TargetFramework>netcoreapp2.0</TargetFramework>
+ <OutputType>Library</OutputType>
<IsPackable>false</IsPackable>
+ <AssemblyKey></AssemblyKey>
+ <AssemblyOriginatorKeyFile>..\StrongNameKeys\ILVerify.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
@@ -24,4 +27,8 @@
<Folder Include="ILTests\" />
</ItemGroup>
+ <ItemGroup>
+ <None Include="..\..\ILVerification\StrongNameKeys\ILVerify.snk" Link="ILVerify.snk" />
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
diff --git a/src/ILVerify/netcoreapp/ILVerify.cs b/src/ILVerify/netcoreapp/ILVerify.cs
new file mode 100644
index 000000000..6158bb254
--- /dev/null
+++ b/src/ILVerify/netcoreapp/ILVerify.cs
@@ -0,0 +1,9 @@
+using System;
+
+class Program
+{
+ static void Main()
+ {
+ Console.WriteLine("Hello world!");
+ }
+}
diff --git a/src/ILVerify/netcoreapp/ILVerify.csproj b/src/ILVerify/netcoreapp/ILVerify.csproj
new file mode 100644
index 000000000..47b7d3145
--- /dev/null
+++ b/src/ILVerify/netcoreapp/ILVerify.csproj
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="System.CommandLine">
+ <Version>0.1.0-e160909-1</Version>
+ </PackageReference>
+ </ItemGroup>
+
+</Project>
diff --git a/src/ILVerify/src/AssemblyInfo.cs b/src/ILVerify/src/AssemblyInfo.cs
new file mode 100644
index 000000000..83d0e55a1
--- /dev/null
+++ b/src/ILVerify/src/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("ILVerification.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100314e026e409db99b50d61628136c49095e67f782a3032832cfe1e61ba2af8264d2cf7d9228bdf611c1027f61b0ca4c87ee1c248cd58241a695520ba78e76d1c672c2b597cfa0ab4526dcae2b5b6f36936c126e59ada3500d656f3424826d0dab452ea407039d2846cf0e4820905eee537fe904a86097b5b2f3aaae000fc08fc3")]
diff --git a/src/ILVerify/src/ILVerify.csproj b/src/ILVerify/src/ILVerify.csproj
index 08bf2e12c..0f5d1e990 100644
--- a/src/ILVerify/src/ILVerify.csproj
+++ b/src/ILVerify/src/ILVerify.csproj
@@ -1,19 +1,51 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
- <TargetFrameworks>netcoreapp2.0;net46</TargetFrameworks>
- <PlatformTarget>AnyCPU</PlatformTarget>
- <CLSCompliant>false</CLSCompliant>
- <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
- <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
+ <RootNamespace>ILVerify</RootNamespace>
+ <AssemblyName>ILVerify</AssemblyName>
+ <AssemblyKey></AssemblyKey>
+ <AssemblyOriginatorKeyFile>..\..\ILVerification\StrongNameKeys\ILVerify.snk</AssemblyOriginatorKeyFile>
+ <TargetFramework>netcoreapp2.0</TargetFramework>
+ <CopyNugetImplementations>false</CopyNugetImplementations>
+ <!-- Force .dll extension even if output type is exe. -->
+ <TargetExt>.dll</TargetExt>
</PropertyGroup>
-
+
<ItemGroup>
<Compile Include="Program.cs" />
</ItemGroup>
<ItemGroup>
- <PackageReference Include="System.CommandLine" Version="0.1.0-e160909-1" />
+ <PackageReference Include="System.CommandLine" Version="0.1.0-e160909-1" />
<ProjectReference Include="..\..\ILVerification\src\ILVerification.csproj" />
</ItemGroup>
+ <ItemGroup>
+ <Compile Include="..\..\Common\src\CommandLine\CommandLineException.cs">
+ <Link>CommandLine\CommandLineException.cs</Link>
+ </Compile>
+ <Compile Include="..\..\Common\src\CommandLine\CommandLineHelpers.cs">
+ <Link>CommandLine\CommandLineHelpers.cs</Link>
+ </Compile>
+ <Compile Include="..\..\Common\src\System\NotImplemented.cs">
+ <Link>System\NotImplemented.cs</Link>
+ </Compile>
+ </ItemGroup>
+
+ <ItemGroup>
+ <None Include="..\..\ILVerification\StrongNameKeys\ILVerify.snk" Link="ILVerify.snk" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Content Include="ILVerify.runtimeconfig.json">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </Content>
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+
+ <PropertyGroup>
+ <RunCommand Condition="'$(OS)' == 'Windows_NT'">$(TargetDir)$(AssemblyName).exe</RunCommand>
+ <RunCommand Condition="'$(OS)' != 'Windows_NT'">$(TargetDir)$(AssemblyName)</RunCommand>
+ <RunArguments>$(StartArguments)</RunArguments>
+ </PropertyGroup>
+
</Project>
diff --git a/src/ILVerify/src/ILVerify.runtimeconfig.json b/src/ILVerify/src/ILVerify.runtimeconfig.json
new file mode 100644
index 000000000..76e177076
--- /dev/null
+++ b/src/ILVerify/src/ILVerify.runtimeconfig.json
@@ -0,0 +1,7 @@
+{
+ "runtimeOptions": {
+ "configProperties": {
+ "Microsoft.NETCore.DotNetHostPolicy.SetAppPaths": true
+ }
+ }
+}
diff --git a/src/ILVerify/src/Program.cs b/src/ILVerify/src/Program.cs
index 1c9b0961b..d748084b8 100644
--- a/src/ILVerify/src/Program.cs
+++ b/src/ILVerify/src/Program.cs
@@ -194,11 +194,12 @@ namespace ILVerify
Write(method.Name);
Write("(");
- if (method.Signature._parameters != null && method.Signature._parameters.Length > 0)
+ if (method.Signature.Length > 0)
{
bool first = true;
- foreach (Internal.TypeSystem.TypeDesc parameter in method.Signature._parameters)
+ for(int i = 0; i < method.Signature.Length; i++)
{
+ Internal.TypeSystem.TypeDesc parameter = method.Signature[0];
if (first)
{
first = false;
diff --git a/src/JitInterface/src/CorInfoImpl.cs b/src/JitInterface/src/CorInfoImpl.cs
index 95b25f804..a9eaceaab 100644
--- a/src/JitInterface/src/CorInfoImpl.cs
+++ b/src/JitInterface/src/CorInfoImpl.cs
@@ -2904,6 +2904,7 @@ namespace Internal.JitInterface
if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_NewObj
|| pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Box
+ || pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Constrained
|| (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Ldtoken && ConstructedEETypeNode.CreationAllowed(td)))
{
helperId = ReadyToRunHelperId.TypeHandle;
diff --git a/src/Native/Runtime/AsmOffsetsVerify.cpp b/src/Native/Runtime/AsmOffsetsVerify.cpp
index 2e7343ce1..13793b7e6 100644
--- a/src/Native/Runtime/AsmOffsetsVerify.cpp
+++ b/src/Native/Runtime/AsmOffsetsVerify.cpp
@@ -44,3 +44,7 @@ class AsmOffsets
#include "AsmOffsets.h"
};
+
+#ifdef _MSC_VER
+namespace { char WorkaroundLNK4221Warning; };
+#endif
diff --git a/src/Native/Runtime/CachedInterfaceDispatch.cpp b/src/Native/Runtime/CachedInterfaceDispatch.cpp
index c49d13129..63a506baf 100644
--- a/src/Native/Runtime/CachedInterfaceDispatch.cpp
+++ b/src/Native/Runtime/CachedInterfaceDispatch.cpp
@@ -510,7 +510,7 @@ COOP_PINVOKE_HELPER(PTR_Code, RhpUpdateDispatchCellCache, (InterfaceDispatchCell
// Publish the new cache by atomically updating both the cache and stub pointers in the indirection
// cell. This returns us a cache to discard which may be NULL (no previous cache), the previous cache
- // value or the cache we just allocated (another thread peformed an update first).
+ // value or the cache we just allocated (another thread performed an update first).
InterfaceDispatchCache * pDiscardedCache = UpdateCellStubAndCache(pCell, pStub, newCacheValue);
if (pDiscardedCache)
DiscardCache(pDiscardedCache);
diff --git a/src/Native/Runtime/RHCodeMan.cpp b/src/Native/Runtime/RHCodeMan.cpp
index 67fbfb83a..b6a173bb2 100644
--- a/src/Native/Runtime/RHCodeMan.cpp
+++ b/src/Native/Runtime/RHCodeMan.cpp
@@ -506,7 +506,7 @@ void ReportScratchRegs(UInt8 firstEncByte, REGDISPLAY * pContext, GCEnumContext
}
// Enumerate all live object references in that function using the virtual register set. Same reference
-// location cannot be enumerated multiple times (but all differenct references pointing to the same object
+// location cannot be enumerated multiple times (but all different references pointing to the same object
// have to be individually enumerated).
// Returns success of operation.
void EECodeManager::EnumGcRefs(MethodGcInfoPointers * pMethodInfo,
diff --git a/src/Native/Runtime/RhConfig.cpp b/src/Native/Runtime/RhConfig.cpp
index b36f30479..f1879ab10 100644
--- a/src/Native/Runtime/RhConfig.cpp
+++ b/src/Native/Runtime/RhConfig.cpp
@@ -68,12 +68,12 @@ UInt32 RhConfig::ReadConfigValue(_In_z_ const TCHAR *wszName, UInt32 uiDefaultVa
//reads a config value from rhconfig.ini into outputBuffer buffer returning the length of the value.
//lazily reads the file so if the file is not yet read, it will read it on first called
//if the file is not avaliable, or unreadable zero will always be returned
-//cchOuputBuffer is the maximum number of characters to write to outputBuffer
+//cchOutputBuffer is the maximum number of characters to write to outputBuffer
//cchOutputBuffer must be a size >= CONFIG_VAL_MAXLEN + 1
-UInt32 RhConfig::GetIniVariable(_In_z_ const TCHAR* configName, _Out_writes_all_(cchOuputBuffer) TCHAR* outputBuffer, _In_ UInt32 cchOuputBuffer)
+UInt32 RhConfig::GetIniVariable(_In_z_ const TCHAR* configName, _Out_writes_all_(cchOutputBuffer) TCHAR* outputBuffer, _In_ UInt32 cchOutputBuffer)
{
//the buffer needs to be big enough to read the value buffer + null terminator
- if (cchOuputBuffer < CONFIG_VAL_MAXLEN + 1)
+ if (cchOutputBuffer < CONFIG_VAL_MAXLEN + 1)
{
return 0;
}
@@ -99,7 +99,7 @@ UInt32 RhConfig::GetIniVariable(_In_z_ const TCHAR* configName, _Out_writes_all_
UInt32 iValue;
- for (iValue = 0; (iValue < CONFIG_VAL_MAXLEN + 1) && (iValue < (Int32)cchOuputBuffer); iValue++)
+ for (iValue = 0; (iValue < CONFIG_VAL_MAXLEN + 1) && (iValue < (Int32)cchOutputBuffer); iValue++)
{
outputBuffer[iValue] = ((ConfigPair*)g_iniSettings)[iSettings].Value[iValue];
diff --git a/src/Native/Runtime/RhConfig.h b/src/Native/Runtime/RhConfig.h
index c033ed6c8..677d97516 100644
--- a/src/Native/Runtime/RhConfig.h
+++ b/src/Native/Runtime/RhConfig.h
@@ -120,8 +120,8 @@ private:
//reads a config value from rhconfig.ini into outputBuffer buffer returning the length of the value.
//lazily reads the file so if the file is not yet read, it will read it on first called
//if the file is not avaliable, or unreadable zero will always be returned
- //cchOuputBuffer is the maximum number of characters to write to outputBuffer
- UInt32 GetIniVariable(_In_z_ const TCHAR* configName, _Out_writes_all_(cchOuputBuffer) TCHAR* outputBuffer, _In_ UInt32 cchOuputBuffer);
+ //cchOutputBuffer is the maximum number of characters to write to outputBuffer
+ UInt32 GetIniVariable(_In_z_ const TCHAR* configName, _Out_writes_all_(cchOutputBuffer) TCHAR* outputBuffer, _In_ UInt32 cchOutputBuffer);
static bool priv_isspace(char c)
{
diff --git a/src/Native/Runtime/StackFrameIterator.cpp b/src/Native/Runtime/StackFrameIterator.cpp
index d0d4f4e51..54773b0b5 100644
--- a/src/Native/Runtime/StackFrameIterator.cpp
+++ b/src/Native/Runtime/StackFrameIterator.cpp
@@ -1870,7 +1870,7 @@ COOP_PINVOKE_HELPER(Boolean, RhpSfiInit, (StackFrameIterator* pThis, PAL_LIMITED
// The stackwalker is intolerant to hijacked threads, as it is largely expecting to be called from C++
// where the hijack state of the thread is invariant. Because we've exposed the iterator out to C#, we
// need to unhijack every time we callback into C++ because the thread could have been hijacked during our
- // time exectuing C#.
+ // time executing C#.
pCurThread->Unhijack();
// Passing NULL is a special-case to request a standard managed stack trace for the current thread.
@@ -1890,7 +1890,7 @@ COOP_PINVOKE_HELPER(Boolean, RhpSfiNext, (StackFrameIterator* pThis, UInt32* puE
// The stackwalker is intolerant to hijacked threads, as it is largely expecting to be called from C++
// where the hijack state of the thread is invariant. Because we've exposed the iterator out to C#, we
// need to unhijack every time we callback into C++ because the thread could have been hijacked during our
- // time exectuing C#.
+ // time executing C#.
ThreadStore::GetCurrentThread()->Unhijack();
const UInt32 MaxTryRegionIdx = 0xFFFFFFFF;
diff --git a/src/Native/Runtime/coreclr/gcinfodecoder.cpp b/src/Native/Runtime/coreclr/gcinfodecoder.cpp
index a7bc3d204..266e7045a 100644
--- a/src/Native/Runtime/coreclr/gcinfodecoder.cpp
+++ b/src/Native/Runtime/coreclr/gcinfodecoder.cpp
@@ -166,7 +166,7 @@ GcInfoDecoder::GcInfoDecoder(
if (hasGSCookie)
{
// Note that normalization as a code offset can be different than
- // normalization as code legnth
+ // normalization as code length
UINT32 normCodeLength = NORMALIZE_CODE_OFFSET(m_CodeLength);
// Decode prolog/epilog information
@@ -330,7 +330,7 @@ GcInfoDecoder::GcInfoDecoder(
else if(flags & DECODE_FOR_RANGES_CALLBACK)
{
// Note that normalization as a code offset can be different than
- // normalization as code legnth
+ // normalization as code length
UINT32 normCodeLength = NORMALIZE_CODE_OFFSET(m_CodeLength);
UINT32 numBitsPerOffset = CeilOfLog2(normCodeLength);
@@ -453,7 +453,7 @@ void GcInfoDecoder::EnumerateInterruptibleRanges (
EnumerateInterruptibleRangesCallback *pCallback,
void * hCallback)
{
- // If no info is found for the call site, we default to fully-interruptbile
+ // If no info is found for the call site, we default to fully-interruptible
LOG((LF_GCROOTS, LL_INFO1000000, "No GC info found for call site at offset %x. Defaulting to fully-interruptible information.\n", (int) m_InstructionOffset));
UINT32 lastInterruptibleRangeStopOffsetNormalized = 0;
@@ -793,7 +793,7 @@ bool GcInfoDecoder::EnumerateLiveSlots(
_ASSERTE(m_NumInterruptibleRanges);
_ASSERTE(numInterruptibleLength);
- // If no info is found for the call site, we default to fully-interruptbile
+ // If no info is found for the call site, we default to fully-interruptible
LOG((LF_GCROOTS, LL_INFO1000000, "No GC info found for call site at offset %x. Defaulting to fully-interruptible information.\n", (int) m_InstructionOffset));
UINT32 numChunks = (numInterruptibleLength + NUM_NORM_CODE_OFFSETS_PER_CHUNK - 1) / NUM_NORM_CODE_OFFSETS_PER_CHUNK;
diff --git a/src/Native/Runtime/module.cpp b/src/Native/Runtime/module.cpp
index af546eb8d..067f361b8 100644
--- a/src/Native/Runtime/module.cpp
+++ b/src/Native/Runtime/module.cpp
@@ -1171,7 +1171,7 @@ void Module::DoCustomImports(ModuleHeader * pModuleHeader)
// obtain address of indirection cell pointing to the EAT for the exporting module
UInt32 **ptrPtrEAT = (UInt32 **)(thisBaseAddress + customImportTable[i].RvaEATAddr);
- // obtain the EAT by derefencing
+ // obtain the EAT by dereferencing
UInt32 *ptrEAT = *ptrPtrEAT;
// obtain the exporting module
diff --git a/src/Native/Runtime/thread.cpp b/src/Native/Runtime/thread.cpp
index ab9effb93..994c21dad 100644
--- a/src/Native/Runtime/thread.cpp
+++ b/src/Native/Runtime/thread.cpp
@@ -522,7 +522,7 @@ void Thread::GcScanRootsWorker(void * pfnEnumCallback, void * pvCallbackData, St
// interface invocation slow paths for instance. Since the original managed call may have passed GC
// references which are unreported by any managed method on the stack at the time of the GC we
// identify (again conservatively) the range of the stack that might contain these references and
- // report everything. Since it should be a very rare occurance indeed that we actually have to do
+ // report everything. Since it should be a very rare occurrence indeed that we actually have to do
// this this, it's considered a better trade-off than storing signature metadata for every potential
// callsite of the type described above.
if (frameIterator.HasStackRangeToReportConservatively())
diff --git a/src/Native/Runtime/windows/CoffNativeCodeManager.cpp b/src/Native/Runtime/windows/CoffNativeCodeManager.cpp
index e02412904..22a37bb92 100644
--- a/src/Native/Runtime/windows/CoffNativeCodeManager.cpp
+++ b/src/Native/Runtime/windows/CoffNativeCodeManager.cpp
@@ -528,7 +528,7 @@ bool CoffNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn
if ((unwindBlockFlags & UBF_FUNC_HAS_EHINFO) != 0)
p += sizeof(int32_t);
- // Decode the GC info for the current method to detemine its return type
+ // Decode the GC info for the current method to determine its return type
GcInfoDecoder decoder(
GCInfoToken(p),
GcInfoDecoderFlags(DECODE_RETURN_KIND),
diff --git a/src/Native/gc/gc.cpp b/src/Native/gc/gc.cpp
index a1360323c..5c673c0be 100644
--- a/src/Native/gc/gc.cpp
+++ b/src/Native/gc/gc.cpp
@@ -3065,7 +3065,7 @@ gc_heap::dt_estimate_reclaim_space_p (gc_tuning_point tp, int gen_number)
}
// DTREVIEW: Right now we only estimate gen2 fragmentation.
-// on 64-bit though we should consider gen1 or even gen0 fragmentatioin as
+// on 64-bit though we should consider gen1 or even gen0 fragmentation as
// well
inline BOOL
gc_heap::dt_estimate_high_frag_p (gc_tuning_point tp, int gen_number, uint64_t available_mem)
@@ -4519,7 +4519,7 @@ gc_heap::soh_get_segment_to_expand()
dprintf (GTC_LOG, ("max_gen-1: Found existing segment to expand into %Ix", (size_t)seg));
// If we return 0 here, the allocator will think since we are short on end
- // of seg we neeed to trigger a full compacting GC. So if sustained low latency
+ // of seg we need to trigger a full compacting GC. So if sustained low latency
// is set we should acquire a new seg instead, that way we wouldn't be short.
// The real solution, of course, is to actually implement seg reuse in gen1.
if (settings.pause_mode != pause_sustained_low_latency)
@@ -5420,7 +5420,7 @@ public:
// Supposedly Pinned objects cannot have references but we are seeing some from pinvoke
// frames. Also if it's an artificially pinned plug created by us, it can certainly
// have references.
- // We know these cases will be rare so we can optimize this to be only allocated on decommand.
+ // We know these cases will be rare so we can optimize this to be only allocated on demand.
gap_reloc_pair saved_post_plug_reloc;
// We need to calculate this after we are done with plan phase and before compact
@@ -6217,7 +6217,7 @@ void gc_heap::set_pinned_info (uint8_t* last_pinned_plug, size_t plug_len, gener
size_t gc_heap::deque_pinned_plug ()
{
- dprintf (3, ("dequed: %Id", mark_stack_bos));
+ dprintf (3, ("deque: %Id", mark_stack_bos));
size_t m = mark_stack_bos;
mark_stack_bos++;
return m;
@@ -7065,7 +7065,7 @@ int gc_heap::grow_brick_card_tables (uint8_t* start,
if ((la != saved_g_lowest_address ) || (ha != saved_g_highest_address))
{
{
- //modify the higest address so the span covered
+ //modify the highest address so the span covered
//is twice the previous one.
uint8_t* top = (uint8_t*)0 + Align (GCToOSInterface::GetVirtualMemoryLimit());
// On non-Windows systems, we get only an approximate value that can possibly be
@@ -14703,7 +14703,7 @@ int gc_heap::generation_to_condemn (int n_initial,
}
}
- //figure out which ephemeral generation is too fragramented
+ //figure out which ephemeral generation is too fragmented
temp_gen = n;
for (i = n+1; i < max_generation; i++)
{
@@ -17404,7 +17404,7 @@ void gc_heap::enque_pinned_plug (uint8_t* plug,
}
}
- dprintf (3, ("enquing P #%Id(%Ix): %Ix. oldest: %Id, LO: %Ix, pre: %d",
+ dprintf (3, ("enqueuing P #%Id(%Ix): %Ix. oldest: %Id, LO: %Ix, pre: %d",
mark_stack_tos, &mark_stack_array[mark_stack_tos], plug, mark_stack_bos, last_object_in_last_plug, (save_pre_plug_info_p ? 1 : 0)));
mark& m = mark_stack_array[mark_stack_tos];
m.first = plug;
@@ -27942,7 +27942,7 @@ void gc_heap::count_plug (size_t last_plug_size, uint8_t*& last_plug)
{
deque_pinned_plug();
update_oldest_pinned_plug();
- dprintf (3, ("dequed pin,now oldest pin is %Ix", pinned_plug (oldest_pin())));
+ dprintf (3, ("deque pin,now oldest pin is %Ix", pinned_plug (oldest_pin())));
}
else
{
@@ -29865,7 +29865,7 @@ size_t gc_heap::joined_youngest_desired (size_t new_allocation)
{
uint32_t memory_load = 0;
get_memory_info (&memory_load);
- dprintf (2, ("Current emory load: %d", memory_load));
+ dprintf (2, ("Current memory load: %d", memory_load));
size_t final_total =
trim_youngest_desired (memory_load, total_new_allocation, total_min_allocation);
@@ -33921,7 +33921,7 @@ static int32_t GCStressCurCount = 0;
static int32_t GCStressStartAtJit = -1;
// the maximum number of foreground GCs we'll induce during one BGC
-// (this number does not include "naturally" occuring GCs).
+// (this number does not include "naturally" occurring GCs).
static int32_t GCStressMaxFGCsPerBGC = -1;
// CLRRandom implementation can produce FPU exceptions if
@@ -35262,7 +35262,7 @@ GCHeap::GarbageCollectGeneration (unsigned int gen, gc_reason reason)
size_t GCHeap::GetTotalBytesInUse ()
{
#ifdef MULTIPLE_HEAPS
- //enumarate all the heaps and get their size.
+ //enumerate all the heaps and get their size.
size_t tot_size = 0;
for (int i = 0; i < gc_heap::n_heaps; i++)
{
@@ -35581,7 +35581,7 @@ HRESULT GCHeap::GetGcCounters(int gen, gc_counters* counters)
counters->promoted_size = 0;
counters->collection_count = 0;
- //enumarate all the heaps and get their counters.
+ //enumerate all the heaps and get their counters.
for (int i = 0; i < gc_heap::n_heaps; i++)
{
dynamic_data* dd = gc_heap::g_heaps [i]->dynamic_data_of (gen);
@@ -35674,7 +35674,7 @@ Object* GCHeap::GetNextFinalizableObject()
if (O)
return O;
}
- //return the first non crtitical/critical one in the first queue.
+ //return the first non critical/critical one in the first queue.
for (int hn = 0; hn < gc_heap::n_heaps; hn++)
{
gc_heap* hp = gc_heap::g_heaps [hn];
diff --git a/src/Native/gc/handletable.cpp b/src/Native/gc/handletable.cpp
index 3be1c1611..2b598528c 100644
--- a/src/Native/gc/handletable.cpp
+++ b/src/Native/gc/handletable.cpp
@@ -1056,7 +1056,7 @@ void HndScanHandlesForGC(HHANDLETABLE hTable, HANDLESCANPROC scanProc, uintptr_t
/*
* HndResetAgeMap
*
- * Service to forceably reset the age map for a set of handles.
+ * Service to forcibly reset the age map for a set of handles.
*
* Provided for GC-time resetting the handle table's write barrier. This is not
* normally advisable, as it increases the amount of work that will be done in
diff --git a/src/Native/gc/handletablecore.cpp b/src/Native/gc/handletablecore.cpp
index 5776c26ac..4137c1645 100644
--- a/src/Native/gc/handletablecore.cpp
+++ b/src/Native/gc/handletablecore.cpp
@@ -129,7 +129,7 @@ void QuickSort(uintptr_t *pData, int left, int right, PFNCOMPARE pfnCompare)
*
* Returns:
* <0 - handle P should be freed before handle Q
- * =0 - handles are eqivalent for free order purposes
+ * =0 - handles are equivalent for free order purposes
* >0 - handle Q should be freed before handle P
*
*/
@@ -242,7 +242,7 @@ BOOL TableCanFreeSegmentNow(HandleTable *pTable, TableSegment *pSegment)
_ASSERTE(threadId.IsCurrentThread());
#endif // _DEBUG
- // deterine if any segment is currently being scanned asynchronously
+ // determine if any segment is currently being scanned asynchronously
TableSegment *pSegmentAsync = NULL;
// do we have async info?
@@ -1852,7 +1852,7 @@ void SegmentTrimExcessPages(TableSegment *pSegment)
// compute the address for the new decommit line
size_t dwDecommitAddr = dwLo - g_SystemInfo.dwPageSize;
- // assume a decommit line of zero until we know otheriwse
+ // assume a decommit line of zero until we know otherwise
uDecommitLine = 0;
// if the address is within the handle area then compute the line from the address
@@ -1869,7 +1869,7 @@ void SegmentTrimExcessPages(TableSegment *pSegment)
/*
* BlockAllocHandlesInMask
*
- * Attempts to allocate the requested number of handes of the specified type,
+ * Attempts to allocate the requested number of handles of the specified type,
* from the specified mask of the specified handle block.
*
* Returns the number of available handles actually allocated.
@@ -2029,7 +2029,7 @@ uint32_t BlockAllocHandlesInitial(TableSegment *pSegment, uint32_t uType, uint32
/*
* BlockAllocHandles
*
- * Attempts to allocate the requested number of handes of the specified type,
+ * Attempts to allocate the requested number of handles of the specified type,
* from the specified handle block.
*
* Returns the number of available handles actually allocated.
@@ -2087,7 +2087,7 @@ uint32_t BlockAllocHandles(TableSegment *pSegment, uint32_t uBlock, OBJECTHANDLE
/*
* SegmentAllocHandlesFromTypeChain
*
- * Attempts to allocate the requested number of handes of the specified type,
+ * Attempts to allocate the requested number of handles of the specified type,
* from the specified segment's block chain for the specified type. This routine
* ONLY scavenges existing blocks in the type chain. No new blocks are committed.
*
@@ -2171,7 +2171,7 @@ uint32_t SegmentAllocHandlesFromTypeChain(TableSegment *pSegment, uint32_t uType
/*
* SegmentAllocHandlesFromFreeList
*
- * Attempts to allocate the requested number of handes of the specified type,
+ * Attempts to allocate the requested number of handles of the specified type,
* by committing blocks from the free list to that type's type chain.
*
* Returns the number of available handles actually allocated.
@@ -2230,7 +2230,7 @@ uint32_t SegmentAllocHandlesFromFreeList(TableSegment *pSegment, uint32_t uType,
/*
* SegmentAllocHandles
*
- * Attempts to allocate the requested number of handes of the specified type,
+ * Attempts to allocate the requested number of handles of the specified type,
* from the specified segment.
*
* Returns the number of available handles actually allocated.
@@ -2268,7 +2268,7 @@ uint32_t SegmentAllocHandles(TableSegment *pSegment, uint32_t uType, OBJECTHANDL
/*
* TableAllocBulkHandles
*
- * Attempts to allocate the requested number of handes of the specified type.
+ * Attempts to allocate the requested number of handles of the specified type.
*
* Returns the number of handles that were actually allocated. This is always
* the same as the number of handles requested except in out-of-memory conditions,
diff --git a/src/Native/gc/handletablescan.cpp b/src/Native/gc/handletablescan.cpp
index 1f2bb33cc..504630b9a 100644
--- a/src/Native/gc/handletablescan.cpp
+++ b/src/Native/gc/handletablescan.cpp
@@ -118,7 +118,7 @@ If you change any of those algorithm, please verify it by this program:
assert (mask == 0);
return;
}
- //any generaion bigger than 2 is actually 2
+ //any generation bigger than 2 is actually 2
if (gen > 2)
gen = 2;
@@ -716,10 +716,10 @@ void CALLBACK BlockScanBlocksEphemeral(PTR_TableSegment pSegment, uint32_t uBloc
uint32_t *pdwGen = (uint32_t *)pSegment->rgGeneration + uBlock;
uint32_t *pdwGenLast = pdwGen + uCount;
- // loop over all the blocks, checking for elligible clumps as we go
+ // loop over all the blocks, checking for eligible clumps as we go
do
{
- // determine if any clumps in this block are elligible
+ // determine if any clumps in this block are eligible
uint32_t dwClumpMask = COMPUTE_CLUMP_MASK(*pdwGen, dwAgeMask);
// if there are any clumps to scan then scan them now
@@ -1434,7 +1434,7 @@ PTR_TableSegment CALLBACK StandardSegmentIterator(PTR_HandleTable pTable, PTR_Ta
PTR_TableSegment pNextSegment = QuickSegmentIterator(pTable, pPrevSegment);
#ifndef DACCESS_COMPILE
- // re-sort the block chains if neccessary
+ // re-sort the block chains if necessary
if (pNextSegment && pNextSegment->fResortChains)
SegmentResortChains(pNextSegment);
#endif
diff --git a/src/Native/gc/objecthandle.cpp b/src/Native/gc/objecthandle.cpp
index 3ce5fdfa4..28dcc5222 100644
--- a/src/Native/gc/objecthandle.cpp
+++ b/src/Native/gc/objecthandle.cpp
@@ -1247,7 +1247,7 @@ void Ref_CheckReachable(uint32_t condemned, uint32_t maxgen, uintptr_t lp1)
// strong handle to refer to the secondary as this could case a cycle in the graph if the secondary somehow
// pointed back to the primary. Can't use weak handle because that would not keep the secondary object alive.
//
-// The result is that a dependenHandle has the EFFECT of
+// The result is that a dependentHandle has the EFFECT of
// * long weak handles in both the primary and secondary objects
// * a strong reference from the primary object to the secondary one
//
diff --git a/src/Native/gc/unix/gcenv.unix.cpp b/src/Native/gc/unix/gcenv.unix.cpp
index e4f9a5b70..07259c0f2 100644
--- a/src/Native/gc/unix/gcenv.unix.cpp
+++ b/src/Native/gc/unix/gcenv.unix.cpp
@@ -11,7 +11,7 @@
// This isn't something we want, because we're totally fine using non-posix functions.
#if defined(__APPLE__)
#define _DARWIN_C_SOURCE
-#endif // definfed(__APPLE__)
+#endif // defined(__APPLE__)
#include <pthread.h>
#include <signal.h>
diff --git a/src/Native/libunwind/src/Unwind-EHABI.cpp b/src/Native/libunwind/src/Unwind-EHABI.cpp
index 863953eb2..18b82f8c8 100644
--- a/src/Native/libunwind/src/Unwind-EHABI.cpp
+++ b/src/Native/libunwind/src/Unwind-EHABI.cpp
@@ -695,7 +695,7 @@ _LIBUNWIND_EXPORT void _Unwind_Complete(_Unwind_Exception* exception_object) {
/// may force a jump to a landing pad in that function, the landing
/// pad code may then call _Unwind_Resume() to continue with the
/// unwinding. Note: the call to _Unwind_Resume() is from compiler
-/// geneated user code. All other _Unwind_* routines are called
+/// generated user code. All other _Unwind_* routines are called
/// by the C++ runtime __cxa_* routines.
///
/// Note: re-throwing an exception (as opposed to continuing the unwind)
diff --git a/src/Native/libunwind/src/Unwind_AppleExtras.cpp b/src/Native/libunwind/src/Unwind_AppleExtras.cpp
index d8301c057..dcde35eec 100644
--- a/src/Native/libunwind/src/Unwind_AppleExtras.cpp
+++ b/src/Native/libunwind/src/Unwind_AppleExtras.cpp
@@ -191,7 +191,7 @@ bool checkKeyMgrRegisteredFDEs(uintptr_t pc, void *&fde) {
_Unwind_FunctionContext *fc_ = nullptr;
#endif
-// Accessors to get get/set linked list of frames for sjlj based execeptions.
+// Accessors to get get/set linked list of frames for sjlj based exceptions.
_LIBUNWIND_HIDDEN
struct _Unwind_FunctionContext *__Unwind_SjLj_GetTopOfFunctionStack() {
#ifndef _LIBUNWIND_HAS_NO_THREADS
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs
index 021f77459..08aa6113d 100644
--- a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs
@@ -22,12 +22,16 @@ internal static partial class Interop
[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOf")]
internal unsafe static extern int IndexOf(SafeSortHandle sortHandle, string target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options, int* matchLengthPtr);
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOf")]
+ internal unsafe static extern int IndexOf(SafeSortHandle sortHandle, char* target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options, int* matchLengthPtr);
[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_LastIndexOf")]
internal unsafe static extern int LastIndexOf(SafeSortHandle sortHandle, string target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options);
[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOfOrdinalIgnoreCase")]
internal unsafe static extern int IndexOfOrdinalIgnoreCase(string target, int cwTargetLength, char* pSource, int cwSourceLength, bool findLast);
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOfOrdinalIgnoreCase")]
+ internal unsafe static extern int IndexOfOrdinalIgnoreCase(char* target, int cwTargetLength, char* pSource, int cwSourceLength, bool findLast);
[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_StartsWith")]
[return: MarshalAs(UnmanagedType.Bool)]
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFullPathNameW.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFullPathNameW.cs
index 15dd58111..b4d4170ae 100644
--- a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFullPathNameW.cs
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFullPathNameW.cs
@@ -13,6 +13,6 @@ internal partial class Interop
/// WARNING: This method does not implicitly handle long paths. Use GetFullPathName or PathHelper.
/// </summary>
[DllImport(Libraries.Kernel32, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)]
- unsafe internal static extern uint GetFullPathNameW(char* path, uint numBufferChars, char[] buffer, IntPtr mustBeZero);
+ internal static extern uint GetFullPathNameW(string path, uint numBufferChars, ref char buffer, IntPtr mustBeZero);
}
}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetLongPathNameW.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetLongPathNameW.cs
index ce04078af..81b4d096f 100644
--- a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetLongPathNameW.cs
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetLongPathNameW.cs
@@ -13,6 +13,6 @@ internal partial class Interop
/// WARNING: This method does not implicitly handle long paths. Use GetFullPath/PathHelper.
/// </summary>
[DllImport(Libraries.Kernel32, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)]
- internal static extern uint GetLongPathNameW(char[] lpszShortPath, char[] lpszLongPath, uint cchBuffer);
+ internal static extern uint GetLongPathNameW(ref char lpszShortPath, ref char lpszLongPath, uint cchBuffer);
}
}
diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
index d0dc4394b..b86412112 100644
--- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
+++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
@@ -241,6 +241,8 @@
<Compile Include="$(MSBuildThisFileDirectory)System\MemberAccessException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Memory.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\MemoryDebugView.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\MemoryExtensions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\MemoryExtensions.Fast.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\MethodAccessException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\MidpointRounding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\MissingMethodException.cs" />
@@ -445,7 +447,6 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\OutAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\PreserveSigAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\SafeBuffer.cs" />
- <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\StringBuffer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\StructLayoutAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnmanagedFunctionPointerAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnmanagedType.cs" />
@@ -486,8 +487,12 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Single.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Span.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Span.Fast.cs" />
- <Compile Include="$(MSBuildThisFileDirectory)System\SpanDebugView.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Span.NonGeneric.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SpanDebugView.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.BinarySearch.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.Byte.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.T.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\String.Manipulation.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\String.Searching.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\StringSpanHelpers.cs" />
@@ -717,7 +722,6 @@
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Path.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PathHelper.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PathInternal.Windows.cs" />
- <Compile Include="$(MSBuildThisFileDirectory)System\IO\PathInternal.Windows.StringBuffer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\DisableMediaInsertionPrompt.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Security\SafeBSTRHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Security\SecureString.Windows.cs" />
diff --git a/src/System.Private.CoreLib/shared/System/Boolean.cs b/src/System.Private.CoreLib/shared/System/Boolean.cs
index c1f4bc9e7..fd56082f9 100644
--- a/src/System.Private.CoreLib/shared/System/Boolean.cs
+++ b/src/System.Private.CoreLib/shared/System/Boolean.cs
@@ -100,7 +100,7 @@ namespace System
{
string s = m_value ? TrueLiteral : FalseLiteral;
- if (s.AsReadOnlySpan().TryCopyTo(destination))
+ if (s.AsSpan().TryCopyTo(destination))
{
charsWritten = s.Length;
return true;
@@ -181,7 +181,7 @@ namespace System
public static Boolean Parse(String value)
{
if (value == null) throw new ArgumentNullException(nameof(value));
- return Parse(value.AsReadOnlySpan());
+ return Parse(value.AsSpan());
}
public static bool Parse(ReadOnlySpan<char> value) =>
@@ -197,19 +197,19 @@ namespace System
return false;
}
- return TryParse(value.AsReadOnlySpan(), out result);
+ return TryParse(value.AsSpan(), out result);
}
public static bool TryParse(ReadOnlySpan<char> value, out bool result)
{
- ReadOnlySpan<char> trueSpan = TrueLiteral.AsReadOnlySpan();
+ ReadOnlySpan<char> trueSpan = TrueLiteral.AsSpan();
if (StringSpanHelpers.Equals(trueSpan, value, StringComparison.OrdinalIgnoreCase))
{
result = true;
return true;
}
- ReadOnlySpan<char> falseSpan = FalseLiteral.AsReadOnlySpan();
+ ReadOnlySpan<char> falseSpan = FalseLiteral.AsSpan();
if (StringSpanHelpers.Equals(falseSpan, value, StringComparison.OrdinalIgnoreCase))
{
result = false;
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/ValueListBuilder.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/ValueListBuilder.cs
index 26b3c9f85..72da4a9e1 100644
--- a/src/System.Private.CoreLib/shared/System/Collections/Generic/ValueListBuilder.cs
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/ValueListBuilder.cs
@@ -43,7 +43,7 @@ namespace System.Collections.Generic
_pos = pos + 1;
}
- public ReadOnlySpan<T> AsReadOnlySpan()
+ public ReadOnlySpan<T> AsSpan()
{
return _span.Slice(0, _pos);
}
diff --git a/src/System.Private.CoreLib/shared/System/Convert.cs b/src/System.Private.CoreLib/shared/System/Convert.cs
index 488ea7733..756bf17fc 100644
--- a/src/System.Private.CoreLib/shared/System/Convert.cs
+++ b/src/System.Private.CoreLib/shared/System/Convert.cs
@@ -2198,7 +2198,7 @@ namespace System
return 0;
}
- int r = ParseNumbers.StringToInt(value.AsReadOnlySpan(), fromBase, ParseNumbers.IsTight | ParseNumbers.TreatAsUnsigned);
+ int r = ParseNumbers.StringToInt(value.AsSpan(), fromBase, ParseNumbers.IsTight | ParseNumbers.TreatAsUnsigned);
if (r < Byte.MinValue || r > Byte.MaxValue)
ThrowByteOverflowException();
return (byte)r;
@@ -2221,7 +2221,7 @@ namespace System
return 0;
}
- int r = ParseNumbers.StringToInt(value.AsReadOnlySpan(), fromBase, ParseNumbers.IsTight | ParseNumbers.TreatAsI1);
+ int r = ParseNumbers.StringToInt(value.AsSpan(), fromBase, ParseNumbers.IsTight | ParseNumbers.TreatAsI1);
if (fromBase != 10 && r <= Byte.MaxValue)
return (sbyte)r;
@@ -2246,7 +2246,7 @@ namespace System
return 0;
}
- int r = ParseNumbers.StringToInt(value.AsReadOnlySpan(), fromBase, ParseNumbers.IsTight | ParseNumbers.TreatAsI2);
+ int r = ParseNumbers.StringToInt(value.AsSpan(), fromBase, ParseNumbers.IsTight | ParseNumbers.TreatAsI2);
if (fromBase != 10 && r <= UInt16.MaxValue)
return (short)r;
@@ -2272,7 +2272,7 @@ namespace System
return 0;
}
- int r = ParseNumbers.StringToInt(value.AsReadOnlySpan(), fromBase, ParseNumbers.IsTight | ParseNumbers.TreatAsUnsigned);
+ int r = ParseNumbers.StringToInt(value.AsSpan(), fromBase, ParseNumbers.IsTight | ParseNumbers.TreatAsUnsigned);
if (r < UInt16.MinValue || r > UInt16.MaxValue)
ThrowUInt16OverflowException();
return (ushort)r;
@@ -2289,7 +2289,7 @@ namespace System
throw new ArgumentException(SR.Arg_InvalidBase);
}
return value != null ?
- ParseNumbers.StringToInt(value.AsReadOnlySpan(), fromBase, ParseNumbers.IsTight) :
+ ParseNumbers.StringToInt(value.AsSpan(), fromBase, ParseNumbers.IsTight) :
0;
}
@@ -2305,7 +2305,7 @@ namespace System
throw new ArgumentException(SR.Arg_InvalidBase);
}
return value != null ?
- (uint)ParseNumbers.StringToInt(value.AsReadOnlySpan(), fromBase, ParseNumbers.TreatAsUnsigned | ParseNumbers.IsTight) :
+ (uint)ParseNumbers.StringToInt(value.AsSpan(), fromBase, ParseNumbers.TreatAsUnsigned | ParseNumbers.IsTight) :
0;
}
@@ -2320,7 +2320,7 @@ namespace System
throw new ArgumentException(SR.Arg_InvalidBase);
}
return value != null ?
- ParseNumbers.StringToLong(value.AsReadOnlySpan(), fromBase, ParseNumbers.IsTight) :
+ ParseNumbers.StringToLong(value.AsSpan(), fromBase, ParseNumbers.IsTight) :
0;
}
@@ -2336,7 +2336,7 @@ namespace System
throw new ArgumentException(SR.Arg_InvalidBase);
}
return value != null ?
- (ulong)ParseNumbers.StringToLong(value.AsReadOnlySpan(), fromBase, ParseNumbers.TreatAsUnsigned | ParseNumbers.IsTight) :
+ (ulong)ParseNumbers.StringToLong(value.AsSpan(), fromBase, ParseNumbers.TreatAsUnsigned | ParseNumbers.IsTight) :
0;
}
@@ -2653,7 +2653,7 @@ namespace System
throw new ArgumentNullException(nameof(s));
}
- return TryFromBase64Chars(s.AsReadOnlySpan(), bytes, out bytesWritten);
+ return TryFromBase64Chars(s.AsSpan(), bytes, out bytesWritten);
}
public static unsafe bool TryFromBase64Chars(ReadOnlySpan<char> chars, Span<byte> bytes, out int bytesWritten)
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.Unix.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.Unix.cs
index 0554581ba..627ea4ab7 100644
--- a/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.Unix.cs
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.Unix.cs
@@ -21,7 +21,19 @@ namespace System.Diagnostics
// In Core, we do not show a dialog.
// Fail in order to avoid anyone catching an exception and masking
// an assert failure.
- var ex = new DebugAssertException(message, detailMessage, stackTrace);
+ DebugAssertException ex;
+ if (message == String.Empty)
+ {
+ ex = new DebugAssertException(stackTrace);
+ }
+ else if (detailMessage == String.Empty)
+ {
+ ex = new DebugAssertException(message, stackTrace);
+ }
+ else
+ {
+ ex = new DebugAssertException(message, detailMessage, stackTrace);
+ }
Environment.FailFast(ex.Message, ex, errorSource);
}
}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.cs
index 3a297387e..7bc43ccfc 100644
--- a/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.cs
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.cs
@@ -113,7 +113,7 @@ namespace System.Diagnostics
string stackTrace;
try
{
- stackTrace = new StackTrace(0, true).ToString(System.Diagnostics.StackTrace.TraceFormat.Normal);
+ stackTrace = new StackTrace(2, true).ToString(System.Diagnostics.StackTrace.TraceFormat.Normal);
}
catch
{
@@ -326,8 +326,18 @@ namespace System.Diagnostics
private sealed class DebugAssertException : Exception
{
+ internal DebugAssertException(string stackTrace) :
+ base(Environment.NewLine + stackTrace)
+ {
+ }
+
+ internal DebugAssertException(string message, string stackTrace) :
+ base(message + Environment.NewLine + Environment.NewLine + stackTrace)
+ {
+ }
+
internal DebugAssertException(string message, string detailMessage, string stackTrace) :
- base(message + Environment.NewLine + detailMessage + Environment.NewLine + stackTrace)
+ base(message + Environment.NewLine + detailMessage + Environment.NewLine + Environment.NewLine + stackTrace)
{
}
}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs
index a6a01b970..40d39ced7 100644
--- a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs
@@ -20,13 +20,13 @@
//
// Conceptually and EventSouce is something takes event logging data from the source methods
// To the EventListener that can subscribe them. Note that CONCEPTUALLY EVENTSOURCES DON'T
-// KNOW ABOUT ETW!. The MODEL of the system is that there is a special EventListern Which
+// KNOW ABOUT ETW!. The MODEL of the system is that there is a special EventListener Which
// we will call the EtwEventListener, that forwards commands from ETW to EventSources and
// listeners to the EventSources and forwards on those events to ETW. THus the model should
// be that you DON'T NEED ETW.
//
// Now in actual practice, EventSouce have rather intimate knowledge of ETW and send events
-// to it directly, but this can be VIEWED AS AN OPTIMIATION.
+// to it directly, but this can be VIEWED AS AN OPTIMIZATION.
//
// Basic Event Data Flow:
//
@@ -656,7 +656,7 @@ namespace System.Diagnostics.Tracing
foreach (var parameter in m_eventData[i].Parameters)
{
// Write parameter type.
- WriteToBuffer(pMetadata, metadataLength, ref offset, (uint)Type.GetTypeCode(parameter.ParameterType));
+ WriteToBuffer(pMetadata, metadataLength, ref offset, (uint)GetTypeCodeExtended(parameter.ParameterType));
// Write parameter name.
string parameterName = parameter.Name;
@@ -698,7 +698,19 @@ namespace System.Diagnostics.Tracing
Debug.Assert(bufferLength >= (offset + 8));
*(long *)(buffer + offset) = value;
offset += 8;
- }
+ }
+
+ private static TypeCode GetTypeCodeExtended(Type parameterType)
+ {
+ // Guid is not part of TypeCode, we decided to use 17 to represent it, as it's the "free slot"
+ // see https://github.com/dotnet/coreclr/issues/16105#issuecomment-361749750 for more
+ const TypeCode GuidTypeCode = (TypeCode)17;
+
+ if (parameterType == typeof(Guid)) // Guid is not a part of TypeCode enum
+ return GuidTypeCode;
+
+ return Type.GetTypeCode(parameterType);
+ }
#endif
internal virtual void GetMetadata(out Guid eventSourceGuid, out string eventSourceName, out EventMetadata[] eventData, out byte[] manifestBytes)
diff --git a/src/System.Private.CoreLib/shared/System/Double.cs b/src/System.Private.CoreLib/shared/System/Double.cs
index 3652963ef..146ee4600 100644
--- a/src/System.Private.CoreLib/shared/System/Double.cs
+++ b/src/System.Private.CoreLib/shared/System/Double.cs
@@ -17,6 +17,8 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
+using Internal.Runtime.CompilerServices;
+
namespace System
{
[Serializable]
@@ -221,16 +223,19 @@ namespace System
//The hashcode for a double is the absolute value of the integer representation
//of that double.
//
- public unsafe override int GetHashCode()
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // 64-bit constants make the IL unusually large that makes the inliner to reject the method
+ public override int GetHashCode()
{
- double d = m_value;
- if (d == 0)
+ var bits = Unsafe.As<double, long>(ref m_value);
+
+ // Optimized check for IsNan() || IsZero()
+ if (((bits - 1) & 0x7FFFFFFFFFFFFFFF) >= 0x7FF0000000000000)
{
- // Ensure that 0 and -0 have the same hash code
- return 0;
+ // Ensure that all NaNs and both zeros have the same hash code
+ bits &= 0x7FF0000000000000;
}
- long value = *(long*)(&d);
- return unchecked((int)value) ^ ((int)(value >> 32));
+
+ return unchecked((int)bits) ^ ((int)(bits >> 32));
}
public override String ToString()
@@ -340,7 +345,7 @@ namespace System
bool success = Number.TryParseDouble(s, style, info, out result);
if (!success)
{
- ReadOnlySpan<char> sTrim = StringSpanHelpers.Trim(s);
+ ReadOnlySpan<char> sTrim = s.Trim();
if (StringSpanHelpers.Equals(sTrim, info.PositiveInfinitySymbol))
{
result = PositiveInfinity;
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Invariant.cs b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Invariant.cs
index 13725bcc5..29e4f5321 100644
--- a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Invariant.cs
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Invariant.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
+using System.Runtime.InteropServices;
namespace System.Globalization
{
@@ -26,6 +27,18 @@ namespace System.Globalization
}
}
+ internal static unsafe int InvariantIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pValue = &MemoryMarshal.GetReference(value))
+ {
+ return InvariantFindString(pSource, source.Length, pValue, value.Length, ignoreCase, start: true);
+ }
+ }
+
internal static unsafe int InvariantLastIndexOf(string source, string value, int startIndex, int count, bool ignoreCase)
{
Debug.Assert(source != null);
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs
index 88af26d90..c369c816b 100644
--- a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs
@@ -298,7 +298,7 @@ namespace System.Globalization
return (Compare(string1, string2, CompareOptions.None));
}
- public unsafe virtual int Compare(string string1, string string2, CompareOptions options)
+ public virtual int Compare(string string1, string string2, CompareOptions options)
{
if (options == CompareOptions.OrdinalIgnoreCase)
{
@@ -344,17 +344,17 @@ namespace System.Globalization
return String.CompareOrdinal(string1, string2);
}
- return CompareString(string1.AsReadOnlySpan(), string2.AsReadOnlySpan(), options);
+ return CompareString(string1.AsSpan(), string2.AsSpan(), options);
}
// TODO https://github.com/dotnet/coreclr/issues/13827:
// This method shouldn't be necessary, as we should be able to just use the overload
// that takes two spans. But due to this issue, that's adding significant overhead.
- internal unsafe int Compare(ReadOnlySpan<char> string1, string string2, CompareOptions options)
+ internal int Compare(ReadOnlySpan<char> string1, string string2, CompareOptions options)
{
if (options == CompareOptions.OrdinalIgnoreCase)
{
- return CompareOrdinalIgnoreCase(string1, string2.AsReadOnlySpan());
+ return CompareOrdinalIgnoreCase(string1, string2.AsSpan());
}
// Verify the options before we do any real comparison.
@@ -365,7 +365,7 @@ namespace System.Globalization
throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options));
}
- return string.CompareOrdinal(string1, string2.AsReadOnlySpan());
+ return string.CompareOrdinal(string1, string2.AsSpan());
}
if ((options & ValidCompareMaskOffFlags) != 0)
@@ -382,15 +382,15 @@ namespace System.Globalization
if (_invariantMode)
{
return (options & CompareOptions.IgnoreCase) != 0 ?
- CompareOrdinalIgnoreCase(string1, string2.AsReadOnlySpan()) :
- string.CompareOrdinal(string1, string2.AsReadOnlySpan());
+ CompareOrdinalIgnoreCase(string1, string2.AsSpan()) :
+ string.CompareOrdinal(string1, string2.AsSpan());
}
return CompareString(string1, string2, options);
}
// TODO https://github.com/dotnet/corefx/issues/21395: Expose this publicly?
- internal unsafe virtual int Compare(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options)
+ internal virtual int Compare(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options)
{
if (options == CompareOptions.OrdinalIgnoreCase)
{
@@ -436,7 +436,7 @@ namespace System.Globalization
////////////////////////////////////////////////////////////////////////
- public unsafe virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2)
+ public virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2)
{
return Compare(string1, offset1, length1, string2, offset2, length2, 0);
}
@@ -526,8 +526,8 @@ namespace System.Globalization
}
return CompareString(
- string1.AsReadOnlySpan().Slice(offset1, length1),
- string2.AsReadOnlySpan().Slice(offset2, length2),
+ string1.AsSpan().Slice(offset1, length1),
+ string2.AsSpan().Slice(offset2, length2),
options);
}
@@ -547,11 +547,11 @@ namespace System.Globalization
// it assumes the strings are Ascii string till we hit non Ascii character in strA or strB and then we continue the comparison by
// calling the OS.
//
- internal static unsafe int CompareOrdinalIgnoreCase(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB)
+ internal static int CompareOrdinalIgnoreCase(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB)
{
Debug.Assert(indexA + lengthA <= strA.Length);
Debug.Assert(indexB + lengthB <= strB.Length);
- return CompareOrdinalIgnoreCase(strA.AsReadOnlySpan().Slice(indexA, lengthA), strB.AsReadOnlySpan().Slice(indexB, lengthB));
+ return CompareOrdinalIgnoreCase(strA.AsSpan().Slice(indexA, lengthA), strB.AsSpan().Slice(indexB, lengthB));
}
internal static unsafe int CompareOrdinalIgnoreCase(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
@@ -566,7 +566,7 @@ namespace System.Globalization
char* b = bp;
// in InvariantMode we support all range and not only the ascii characters.
- char maxChar = (char) (GlobalizationMode.Invariant ? 0xFFFF : 0x80);
+ char maxChar = (char) (GlobalizationMode.Invariant ? 0xFFFF : 0x7F);
while (length != 0 && (*a <= maxChar) && (*b <= maxChar))
{
@@ -910,6 +910,18 @@ namespace System.Globalization
return IndexOfCore(source, value, startIndex, count, options, null);
}
+ internal virtual int IndexOfOrdinal(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(!_invariantMode);
+ return IndexOfOrdinalCore(source, value, ignoreCase);
+ }
+
+ internal unsafe virtual int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+ return IndexOfCore(source, value, options, null);
+ }
+
// The following IndexOf overload is mainly used by String.Replace. This overload assumes the parameters are already validated
// and the caller is passing a valid matchLengthPtr pointer.
internal unsafe int IndexOf(string source, string value, int startIndex, int count, CompareOptions options, int* matchLengthPtr)
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeParse.cs b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeParse.cs
index 049aae360..5b285eb5d 100644
--- a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeParse.cs
+++ b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeParse.cs
@@ -5045,7 +5045,7 @@ new DS[] { DS.ERROR, DS.TX_NNN, DS.TX_NNN, DS.TX_NNN, DS.ERROR, DS.ERROR,
{
return false;
}
- if (m_info.Compare(Value.Slice(thisPosition, segmentLength), target.AsReadOnlySpan().Slice(targetPosition, segmentLength), CompareOptions.IgnoreCase) != 0)
+ if (m_info.Compare(Value.Slice(thisPosition, segmentLength), target.AsSpan().Slice(targetPosition, segmentLength), CompareOptions.IgnoreCase) != 0)
{
return false;
}
@@ -5071,7 +5071,7 @@ new DS[] { DS.ERROR, DS.TX_NNN, DS.TX_NNN, DS.TX_NNN, DS.ERROR, DS.ERROR,
{
return false;
}
- if (m_info.Compare(Value.Slice(thisPosition, segmentLength), target.AsReadOnlySpan().Slice(targetPosition, segmentLength), CompareOptions.IgnoreCase) != 0)
+ if (m_info.Compare(Value.Slice(thisPosition, segmentLength), target.AsSpan().Slice(targetPosition, segmentLength), CompareOptions.IgnoreCase) != 0)
{
return false;
}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.Win32.cs b/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.Win32.cs
index 09b1f20c4..365942cce 100644
--- a/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.Win32.cs
+++ b/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.Win32.cs
@@ -69,7 +69,7 @@ namespace System.Globalization
{
try
{
- int advance = Int32.Parse(str.AsReadOnlySpan().Slice(HijriAdvanceRegKeyEntry.Length), provider:CultureInfo.InvariantCulture);
+ int advance = Int32.Parse(str.AsSpan().Slice(HijriAdvanceRegKeyEntry.Length), provider:CultureInfo.InvariantCulture);
if ((advance >= MinAdvancedHijri) && (advance <= MaxAdvancedHijri))
{
hijriAdvance = advance;
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.Win32.cs b/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.Win32.cs
index 9ea6c21c2..1d0180b00 100644
--- a/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.Win32.cs
+++ b/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.Win32.cs
@@ -159,7 +159,7 @@ namespace System.Globalization
int month;
int day;
- ReadOnlySpan<char> valueSpan = value.AsReadOnlySpan();
+ ReadOnlySpan<char> valueSpan = value.AsSpan();
if (!Int32.TryParse(valueSpan.Slice(0, 4), NumberStyles.None, NumberFormatInfo.InvariantInfo, out year) ||
!Int32.TryParse(valueSpan.Slice(5, 2), NumberStyles.None, NumberFormatInfo.InvariantInfo, out month) ||
!Int32.TryParse(valueSpan.Slice(8, 2), NumberStyles.None, NumberFormatInfo.InvariantInfo, out day))
diff --git a/src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs b/src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs
index 5d07f726c..fd24cc810 100644
--- a/src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs
+++ b/src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs
@@ -61,7 +61,7 @@ namespace System.IO
if (IsPathFullyQualified(path))
return GetFullPath(path);
- return GetFullPath(CombineNoChecks(basePath, path));
+ return GetFullPath(CombineInternal(basePath, path));
}
private static string RemoveLongPathPrefix(string path)
@@ -108,7 +108,7 @@ namespace System.IO
if (path == null)
return false;
- return IsPathRooted(path.AsReadOnlySpan());
+ return IsPathRooted(path.AsSpan());
}
public static bool IsPathRooted(ReadOnlySpan<char> path)
@@ -128,7 +128,7 @@ namespace System.IO
public static ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path)
{
- return PathInternal.IsEffectivelyEmpty(path) && IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString.AsReadOnlySpan() : ReadOnlySpan<char>.Empty;
+ return PathInternal.IsEffectivelyEmpty(path) && IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString.AsSpan() : ReadOnlySpan<char>.Empty;
}
/// <summary>Gets whether the system is case-sensitive.</summary>
diff --git a/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs b/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs
index b0443c5fd..b921db9e6 100644
--- a/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs
+++ b/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs
@@ -27,64 +27,32 @@ namespace System.IO
(char)31
};
- // Expands the given path to a fully qualified path.
+ // Expands the given path to a fully qualified path.
public static string GetFullPath(string path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
- // Embedded null characters are the only invalid character case we want to check up front.
+ // If the path would normalize to string empty, we'll consider it empty
+ if (PathInternal.IsEffectivelyEmpty(path))
+ throw new ArgumentException(SR.Arg_PathEmpty, nameof(path));
+
+ // Embedded null characters are the only invalid character case we trully care about.
// This is because the nulls will signal the end of the string to Win32 and therefore have
- // unpredictable results. Other invalid characters we give a chance to be normalized out.
+ // unpredictable results.
if (path.IndexOf('\0') != -1)
throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
if (PathInternal.IsExtended(path))
{
- // We can't really know what is valid for all cases of extended paths.
- //
- // - object names can include other characters as well (':', '/', etc.)
- // - even file objects have different rules (pipe names can contain most characters)
- //
- // As such we will do no further analysis of extended paths to avoid blocking known and unknown
- // scenarios as well as minimizing compat breaks should we block now and need to unblock later.
+ // \\?\ paths are considered normalized by definition. Windows doesn't normalize \\?\
+ // paths and neither should we. Even if we wanted to GetFullPathName does not work
+ // properly with device paths. If one wants to pass a \\?\ path through normalization
+ // one can chop off the prefix, pass it to GetFullPath and add it again.
return path;
}
- bool isDevice = PathInternal.IsDevice(path);
- if (!isDevice)
- {
- // Toss out paths with colons that aren't a valid drive specifier.
- // Cannot start with a colon and can only be of the form "C:".
- // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.)
- int startIndex = PathInternal.PathStartSkip(path);
-
- // Move past the colon
- startIndex += 2;
-
- if ((path.Length > 0 && path[0] == PathInternal.VolumeSeparatorChar)
- || (path.Length >= startIndex && path[startIndex - 1] == PathInternal.VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[startIndex - 2]))
- || (path.Length > startIndex && path.IndexOf(PathInternal.VolumeSeparatorChar, startIndex) != -1))
- {
- throw new NotSupportedException(SR.Format(SR.Argument_PathFormatNotSupported_Path, path));
- }
- }
-
- // Technically this doesn't matter but we used to throw for this case
- if (PathInternal.IsEffectivelyEmpty(path))
- throw new ArgumentException(SR.Arg_PathEmpty, nameof(path));
-
- // We don't want to check invalid characters for device format- see comments for extended above
- string fullPath = PathHelper.Normalize(path, checkInvalidCharacters: !isDevice, expandShortPaths: true);
-
- if (!isDevice)
- {
- // Emulate FileIOPermissions checks, retained for compatibility (normal invalid characters have already been checked)
- if (PathInternal.HasWildCardCharacters(fullPath))
- throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
- }
-
- return fullPath;
+ return PathHelper.Normalize(path);
}
public static string GetFullPath(string path, string basePath)
@@ -112,26 +80,30 @@ namespace System.IO
// Path is current drive rooted i.e. starts with \:
// "\Foo" and "C:\Bar" => "C:\Foo"
// "\Foo" and "\\?\C:\Bar" => "\\?\C:\Foo"
- combinedPath = CombineNoChecks(GetPathRoot(basePath), path.AsReadOnlySpan().Slice(1));
+ combinedPath = Join(GetPathRoot(basePath.AsSpan()), path);
}
else if (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar)
{
// Drive relative paths
Debug.Assert(length == 2 || !PathInternal.IsDirectorySeparator(path[2]));
- if (StringSpanHelpers.Equals(GetVolumeName(path.AsReadOnlySpan()), GetVolumeName(basePath.AsReadOnlySpan())))
+ if (StringSpanHelpers.Equals(GetVolumeName(path), GetVolumeName(basePath)))
{
// Matching root
// "C:Foo" and "C:\Bar" => "C:\Bar\Foo"
// "C:Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo"
- combinedPath = CombineNoChecks(basePath, path.AsReadOnlySpan().Slice(2));
+ combinedPath = Join(basePath, path.AsSpan().Slice(2));
}
else
{
// No matching root, root to specified drive
// "D:Foo" and "C:\Bar" => "D:Foo"
- // "D:\Foo" and "\\?\C:\Bar" => "\\?\D:\Foo"
- combinedPath = path.Insert(2, "\\");
+ // "D:Foo" and "\\?\C:\Bar" => "\\?\D:\Foo"
+ combinedPath = !PathInternal.IsDevice(basePath)
+ ? path.Insert(2, @"\")
+ : length == 2
+ ? JoinInternal(basePath.AsSpan().Slice(0, 4), path, @"\")
+ : JoinInternal(basePath.AsSpan().Slice(0, 4), path.AsSpan().Slice(0, 2), @"\", path.AsSpan().Slice(2));
}
}
else
@@ -139,7 +111,7 @@ namespace System.IO
// "Simple" relative path
// "Foo" and "C:\Bar" => "C:\Bar\Foo"
// "Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo"
- combinedPath = CombineNoChecks(basePath, path);
+ combinedPath = JoinInternal(basePath, path);
}
// Device paths are normalized by definition, so passing something of this format
@@ -176,7 +148,7 @@ namespace System.IO
// if it starts with a backslash ("\") or a valid drive letter and a colon (":").
public static bool IsPathRooted(string path)
{
- return path != null && IsPathRooted(path.AsReadOnlySpan());
+ return path != null && IsPathRooted(path.AsSpan());
}
public static bool IsPathRooted(ReadOnlySpan<char> path)
@@ -200,7 +172,7 @@ namespace System.IO
if (PathInternal.IsEffectivelyEmpty(path))
return null;
- ReadOnlySpan<char> result = GetPathRoot(path.AsReadOnlySpan());
+ ReadOnlySpan<char> result = GetPathRoot(path.AsSpan());
if (path.Length == result.Length)
return PathInternal.NormalizeDirectorySeparators(path);
@@ -248,19 +220,11 @@ namespace System.IO
}
/// <summary>
- /// Returns true if the path ends in a directory separator.
- /// </summary>
- internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path)
- {
- return path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]);
- }
-
- /// <summary>
/// Trims the ending directory separator if present.
/// </summary>
/// <param name="path"></param>
internal static ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path) =>
- EndsInDirectorySeparator(path) ?
+ PathInternal.EndsInDirectorySeparator(path) ?
path.Slice(0, path.Length - 1) :
path;
diff --git a/src/System.Private.CoreLib/shared/System/IO/Path.cs b/src/System.Private.CoreLib/shared/System/IO/Path.cs
index 079eab27f..41ae1cd0b 100644
--- a/src/System.Private.CoreLib/shared/System/IO/Path.cs
+++ b/src/System.Private.CoreLib/shared/System/IO/Path.cs
@@ -79,7 +79,7 @@ namespace System.IO
/// </remarks>
public static string GetDirectoryName(string path)
{
- if (PathInternal.IsEffectivelyEmpty(path))
+ if (path == null || PathInternal.IsEffectivelyEmpty(path))
return null;
int end = GetDirectoryNameOffset(path);
@@ -119,16 +119,18 @@ namespace System.IO
return end;
}
- // Returns the extension of the given path. The returned value includes the
- // period (".") character of the extension except when you have a terminal period when you get string.Empty, such as ".exe" or
- // ".cpp". The returned value is null if the given path is
- // null or if the given path does not include an extension.
+ /// <summary>
+ /// Returns the extension of the given path. The returned value includes the period (".") character of the
+ /// extension except when you have a terminal period when you get string.Empty, such as ".exe" or ".cpp".
+ /// The returned value is null if the given path is null or empty if the given path does not include an
+ /// extension.
+ /// </summary>
public static string GetExtension(string path)
{
if (path == null)
return null;
- return new string(GetExtension(path.AsReadOnlySpan()));
+ return new string(GetExtension(path.AsSpan()));
}
/// <summary>
@@ -157,15 +159,17 @@ namespace System.IO
return ReadOnlySpan<char>.Empty;
}
- // Returns the name and extension parts of the given path. The resulting
- // string contains the characters of path that follow the last
- // separator in path. The resulting string is null if path is null.
+ /// <summary>
+ /// Returns the name and extension parts of the given path. The resulting string contains
+ /// the characters of path that follow the last separator in path. The resulting string is
+ /// null if path is null.
+ /// </summary>
public static string GetFileName(string path)
{
if (path == null)
return null;
- ReadOnlySpan<char> result = GetFileName(path.AsReadOnlySpan());
+ ReadOnlySpan<char> result = GetFileName(path.AsSpan());
if (path.Length == result.Length)
return path;
@@ -196,7 +200,7 @@ namespace System.IO
if (path == null)
return null;
- ReadOnlySpan<char> result = GetFileNameWithoutExtension(path.AsReadOnlySpan());
+ ReadOnlySpan<char> result = GetFileNameWithoutExtension(path.AsSpan());
if (path.Length == result.Length)
return path;
@@ -215,8 +219,10 @@ namespace System.IO
fileName.Slice(0, lastPeriod);
}
- // Returns a cryptographically strong random 8.3 string that can be
- // used as either a folder name or a file name.
+ /// <summary>
+ /// Returns a cryptographically strong random 8.3 string that can be
+ /// used as either a folder name or a file name.
+ /// </summary>
public static unsafe string GetRandomFileName()
{
byte* pKey = stackalloc byte[KeyLength];
@@ -248,7 +254,7 @@ namespace System.IO
if (path == null)
throw new ArgumentNullException(nameof(path));
- return IsPathFullyQualified(path.AsReadOnlySpan());
+ return IsPathFullyQualified(path.AsSpan());
}
public static bool IsPathFullyQualified(ReadOnlySpan<char> path)
@@ -256,15 +262,15 @@ namespace System.IO
return !PathInternal.IsPartiallyQualified(path);
}
- // Tests if a path includes a file extension. The result is
- // true if the characters that follow the last directory
- // separator ('\\' or '/') or volume separator (':') in the path include
- // a period (".") other than a terminal period. The result is false otherwise.
+ /// <summary>
+ /// Tests if a path's file name includes a file extension. A trailing period
+ /// is not considered an extension.
+ /// </summary>
public static bool HasExtension(string path)
{
if (path != null)
{
- return HasExtension(path.AsReadOnlySpan());
+ return HasExtension(path.AsSpan());
}
return false;
}
@@ -289,7 +295,7 @@ namespace System.IO
if (path1 == null || path2 == null)
throw new ArgumentNullException((path1 == null) ? nameof(path1) : nameof(path2));
- return CombineNoChecks(path1, path2);
+ return CombineInternal(path1, path2);
}
public static string Combine(string path1, string path2, string path3)
@@ -297,7 +303,7 @@ namespace System.IO
if (path1 == null || path2 == null || path3 == null)
throw new ArgumentNullException((path1 == null) ? nameof(path1) : (path2 == null) ? nameof(path2) : nameof(path3));
- return CombineNoChecks(path1, path2, path3);
+ return CombineInternal(path1, path2, path3);
}
public static string Combine(string path1, string path2, string path3, string path4)
@@ -305,7 +311,7 @@ namespace System.IO
if (path1 == null || path2 == null || path3 == null || path4 == null)
throw new ArgumentNullException((path1 == null) ? nameof(path1) : (path2 == null) ? nameof(path2) : (path3 == null) ? nameof(path3) : nameof(path4));
- return CombineNoChecks(path1, path2, path3, path4);
+ return CombineInternal(path1, path2, path3, path4);
}
public static string Combine(params string[] paths)
@@ -376,11 +382,102 @@ namespace System.IO
return StringBuilderCache.GetStringAndRelease(finalPath);
}
- /// <summary>
- /// Combines two paths. Does no validation of paths, only concatenates the paths
- /// and places a directory separator between them if needed.
- /// </summary>
- private static string CombineNoChecks(ReadOnlySpan<char> first, ReadOnlySpan<char> second)
+ // Unlike Combine(), Join() methods do not consider rooting. They simply combine paths, ensuring that there
+ // is a directory separator between them.
+
+ public static string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2)
+ {
+ if (path1.Length == 0)
+ return new string(path2);
+ if (path2.Length == 0)
+ return new string(path1);
+
+ return JoinInternal(path1, path2);
+ }
+
+ public static string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3)
+ {
+ if (path1.Length == 0)
+ return Join(path2, path3);
+
+ if (path2.Length == 0)
+ return Join(path1, path3);
+
+ if (path3.Length == 0)
+ return Join(path1, path2);
+
+ return JoinInternal(path1, path2, path3);
+ }
+
+ public static bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, Span<char> destination, out int charsWritten)
+ {
+ charsWritten = 0;
+ if (path1.Length == 0 && path2.Length == 0)
+ return true;
+
+ if (path1.Length == 0 || path2.Length == 0)
+ {
+ ref ReadOnlySpan<char> pathToUse = ref path1.Length == 0 ? ref path2 : ref path1;
+ if (destination.Length < pathToUse.Length)
+ {
+ return false;
+ }
+
+ pathToUse.CopyTo(destination);
+ charsWritten = pathToUse.Length;
+ return true;
+ }
+
+ bool needsSeparator = !(PathInternal.EndsInDirectorySeparator(path1) || PathInternal.StartsWithDirectorySeparator(path2));
+ int charsNeeded = path1.Length + path2.Length + (needsSeparator ? 1 : 0);
+ if (destination.Length < charsNeeded)
+ return false;
+
+ path1.CopyTo(destination);
+ if (needsSeparator)
+ destination[path1.Length] = DirectorySeparatorChar;
+
+ path2.CopyTo(destination.Slice(path1.Length + (needsSeparator ? 1 : 0)));
+
+ charsWritten = charsNeeded;
+ return true;
+ }
+
+ public static bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3, Span<char> destination, out int charsWritten)
+ {
+ charsWritten = 0;
+ if (path1.Length == 0 && path2.Length == 0 && path3.Length == 0)
+ return true;
+
+ if (path1.Length == 0)
+ return TryJoin(path2, path3, destination, out charsWritten);
+ if (path2.Length == 0)
+ return TryJoin(path1, path3, destination, out charsWritten);
+ if (path3.Length == 0)
+ return TryJoin(path1, path2, destination, out charsWritten);
+
+ int neededSeparators = PathInternal.EndsInDirectorySeparator(path1) || PathInternal.StartsWithDirectorySeparator(path2) ? 0 : 1;
+ bool needsSecondSeparator = !(PathInternal.EndsInDirectorySeparator(path2) || PathInternal.StartsWithDirectorySeparator(path3));
+ if (needsSecondSeparator)
+ neededSeparators++;
+
+ int charsNeeded = path1.Length + path2.Length + path3.Length + neededSeparators;
+ if (destination.Length < charsNeeded)
+ return false;
+
+ bool result = TryJoin(path1, path2, destination, out charsWritten);
+ Debug.Assert(result, "should never fail joining first two paths");
+
+ if (needsSecondSeparator)
+ destination[charsWritten++] = DirectorySeparatorChar;
+
+ path3.CopyTo(destination.Slice(charsWritten));
+ charsWritten += path3.Length;
+
+ return true;
+ }
+
+ private static string CombineInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second)
{
if (first.Length == 0)
return second.Length == 0
@@ -393,10 +490,10 @@ namespace System.IO
if (IsPathRooted(second))
return new string(second);
- return CombineNoChecksInternal(first, second);
+ return JoinInternal(first, second);
}
- private static string CombineNoChecks(string first, string second)
+ private static string CombineInternal(string first, string second)
{
if (string.IsNullOrEmpty(first))
return second;
@@ -404,89 +501,51 @@ namespace System.IO
if (string.IsNullOrEmpty(second))
return first;
- if (IsPathRooted(second.AsReadOnlySpan()))
+ if (IsPathRooted(second.AsSpan()))
return second;
- return CombineNoChecksInternal(first, second);
- }
-
- private static string CombineNoChecks(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third)
- {
- if (first.Length == 0)
- return CombineNoChecks(second, third);
- if (second.Length == 0)
- return CombineNoChecks(first, third);
- if (third.Length == 0)
- return CombineNoChecks(first, second);
-
- if (IsPathRooted(third))
- return new string(third);
- if (IsPathRooted(second))
- return CombineNoChecks(second, third);
-
- return CombineNoChecksInternal(first, second, third);
+ return JoinInternal(first, second);
}
- private static string CombineNoChecks(string first, string second, string third)
+ private static string CombineInternal(string first, string second, string third)
{
if (string.IsNullOrEmpty(first))
- return CombineNoChecks(second, third);
+ return CombineInternal(second, third);
if (string.IsNullOrEmpty(second))
- return CombineNoChecks(first, third);
+ return CombineInternal(first, third);
if (string.IsNullOrEmpty(third))
- return CombineNoChecks(first, second);
+ return CombineInternal(first, second);
- if (IsPathRooted(third.AsReadOnlySpan()))
+ if (IsPathRooted(third.AsSpan()))
return third;
- if (IsPathRooted(second.AsReadOnlySpan()))
- return CombineNoChecks(second, third);
+ if (IsPathRooted(second.AsSpan()))
+ return CombineInternal(second, third);
- return CombineNoChecksInternal(first, second, third);
+ return JoinInternal(first, second, third);
}
- private static string CombineNoChecks(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third, ReadOnlySpan<char> fourth)
- {
- if (first.Length == 0)
- return CombineNoChecks(second, third, fourth);
- if (second.Length == 0)
- return CombineNoChecks(first, third, fourth);
- if (third.Length == 0)
- return CombineNoChecks(first, second, fourth);
- if (fourth.Length == 0)
- return CombineNoChecks(first, second, third);
-
- if (IsPathRooted(fourth))
- return new string(fourth);
- if (IsPathRooted(third))
- return CombineNoChecks(third, fourth);
- if (IsPathRooted(second))
- return CombineNoChecks(second, third, fourth);
-
- return CombineNoChecksInternal(first, second, third, fourth);
- }
-
- private static string CombineNoChecks(string first, string second, string third, string fourth)
+ private static string CombineInternal(string first, string second, string third, string fourth)
{
if (string.IsNullOrEmpty(first))
- return CombineNoChecks(second, third, fourth);
+ return CombineInternal(second, third, fourth);
if (string.IsNullOrEmpty(second))
- return CombineNoChecks(first, third, fourth);
+ return CombineInternal(first, third, fourth);
if (string.IsNullOrEmpty(third))
- return CombineNoChecks(first, second, fourth);
+ return CombineInternal(first, second, fourth);
if (string.IsNullOrEmpty(fourth))
- return CombineNoChecks(first, second, third);
+ return CombineInternal(first, second, third);
- if (IsPathRooted(fourth.AsReadOnlySpan()))
+ if (IsPathRooted(fourth.AsSpan()))
return fourth;
- if (IsPathRooted(third.AsReadOnlySpan()))
- return CombineNoChecks(third, fourth);
- if (IsPathRooted(second.AsReadOnlySpan()))
- return CombineNoChecks(second, third, fourth);
+ if (IsPathRooted(third.AsSpan()))
+ return CombineInternal(third, fourth);
+ if (IsPathRooted(second.AsSpan()))
+ return CombineInternal(second, third, fourth);
- return CombineNoChecksInternal(first, second, third, fourth);
+ return JoinInternal(first, second, third, fourth);
}
- private unsafe static string CombineNoChecksInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second)
+ private unsafe static string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second)
{
Debug.Assert(first.Length > 0 && second.Length > 0, "should have dealt with empty paths");
@@ -508,7 +567,7 @@ namespace System.IO
}
}
- private unsafe static string CombineNoChecksInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third)
+ private unsafe static string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third)
{
Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0, "should have dealt with empty paths");
@@ -536,7 +595,7 @@ namespace System.IO
}
}
- private unsafe static string CombineNoChecksInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third, ReadOnlySpan<char> fourth)
+ private unsafe static string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third, ReadOnlySpan<char> fourth)
{
Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0 && fourth.Length > 0, "should have dealt with empty paths");
@@ -801,9 +860,6 @@ namespace System.IO
return StringBuilderCache.GetStringAndRelease(sb);
}
- // StringComparison and IsCaseSensitive are also available in PathInternal.CaseSensitivity but we are
- // too low in System.Runtime.Extensions to use it (no FileStream, etc.)
-
/// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
internal static StringComparison StringComparison
{
diff --git a/src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs b/src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs
index 6aa01d049..039a28834 100644
--- a/src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs
+++ b/src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs
@@ -3,8 +3,8 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Text;
namespace System.IO
{
@@ -13,194 +13,61 @@ namespace System.IO
/// </summary>
internal class PathHelper
{
- // Can't be over 8.3 and be a short name
- private const int MaxShortName = 12;
-
- private const char LastAnsi = (char)255;
- private const char Delete = (char)127;
-
/// <summary>
/// Normalize the given path.
/// </summary>
/// <remarks>
- /// Normalizes via Win32 GetFullPathName(). Will also trim initial
- /// spaces if the path is determined to be rooted.
- ///
- /// Note that invalid characters will be checked after the path is normalized, which could remove bad characters. (C:\|\..\a.txt -- C:\a.txt)
+ /// Normalizes via Win32 GetFullPathName().
/// </remarks>
/// <param name="path">Path to normalize</param>
- /// <param name="checkInvalidCharacters">True to check for invalid characters</param>
- /// <param name="expandShortPaths">Attempt to expand short paths if true</param>
- /// <exception cref="ArgumentException">Thrown if the path is an illegal UNC (does not contain a full server/share) or contains illegal characters.</exception>
- /// <exception cref="PathTooLongException">Thrown if the path or a path segment exceeds the filesystem limits.</exception>
- /// <exception cref="FileNotFoundException">Thrown if Windows returns ERROR_FILE_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception>
- /// <exception cref="DirectoryNotFoundException">Thrown if Windows returns ERROR_PATH_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception>
- /// <exception cref="UnauthorizedAccessException">Thrown if Windows returns ERROR_ACCESS_DENIED. (See Win32Marshal.GetExceptionForWin32Error)</exception>
- /// <exception cref="IOException">Thrown if Windows returns an error that doesn't map to the above. (See Win32Marshal.GetExceptionForWin32Error)</exception>
+ /// <exception cref="PathTooLongException">Thrown if we have a string that is too large to fit into a UNICODE_STRING.</exception>
+ /// <exception cref="IOException">Thrown if the path is empty.</exception>
/// <returns>Normalized path</returns>
- internal static string Normalize(string path, bool checkInvalidCharacters, bool expandShortPaths)
+ internal static string Normalize(string path)
{
- // Get the full path
- StringBuffer fullPath = new StringBuffer(PathInternal.MaxShortPath);
-
- try
- {
- GetFullPathName(path, ref fullPath);
-
- // Checking path validity used to happen before getting the full path name. To avoid additional input allocation
- // (to trim trailing whitespace) we now do it after the Win32 call. This will allow legitimate paths through that
- // used to get kicked back (notably segments with invalid characters might get removed via "..").
- //
- // There is no way that GetLongPath can invalidate the path so we'll do this (cheaper) check before we attempt to
- // expand short file names.
-
- // Scan the path for:
- //
- // - Illegal path characters.
- // - Invalid UNC paths like \\, \\server, \\server\.
-
- // As the path could be > 30K, we'll combine the validity scan. None of these checks are performed by the Win32
- // GetFullPathName() API.
-
- bool possibleShortPath = false;
- bool foundTilde = false;
-
- // We can get UNCs as device paths through this code (e.g. \\.\UNC\), we won't validate them as there isn't
- // an easy way to normalize without extensive cost (we'd have to hunt down the canonical name for any device
- // path that contains UNC or to see if the path was doing something like \\.\GLOBALROOT\Device\Mup\,
- // \\.\GLOBAL\UNC\, \\.\GLOBALROOT\GLOBAL??\UNC\, etc.
- bool specialPath = fullPath.Length > 1 && fullPath[0] == '\\' && fullPath[1] == '\\';
- bool isDevice = PathInternal.IsDevice(ref fullPath);
- bool possibleBadUnc = specialPath && !isDevice;
- int index = specialPath ? 2 : 0;
- int lastSeparator = specialPath ? 1 : 0;
- int segmentLength;
- char current;
-
- while (index < fullPath.Length)
- {
- current = fullPath[index];
-
- // Try to skip deeper analysis. '?' and higher are valid/ignorable except for '\', '|', and '~'
- if (current < '?' || current == '\\' || current == '|' || current == '~')
- {
- switch (current)
- {
- case '|':
- case '>':
- case '<':
- case '\"':
- if (checkInvalidCharacters) throw new ArgumentException(SR.Argument_InvalidPathChars);
- foundTilde = false;
- break;
- case '~':
- foundTilde = true;
- break;
- case '\\':
- segmentLength = index - lastSeparator - 1;
- lastSeparator = index;
-
- if (foundTilde)
- {
- if (segmentLength <= MaxShortName)
- {
- // Possibly a short path.
- possibleShortPath = true;
- }
-
- foundTilde = false;
- }
-
- if (possibleBadUnc)
- {
- // If we're at the end of the path and this is the first separator, we're missing the share.
- // Otherwise we're good, so ignore UNC tracking from here.
- if (index == fullPath.Length - 1)
- throw new ArgumentException(SR.Format(SR.Arg_PathIllegalUNC_Path, fullPath.ToString()));
- else
- possibleBadUnc = false;
- }
-
- break;
-
- default:
- if (checkInvalidCharacters && current < ' ') throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
- break;
- }
- }
-
- index++;
- }
-
- if (possibleBadUnc)
- throw new ArgumentException(SR.Format(SR.Arg_PathIllegalUNC_Path, fullPath.ToString()));
+ Span<char> initialBuffer = stackalloc char[PathInternal.MaxShortPath];
+ ValueStringBuilder builder = new ValueStringBuilder(initialBuffer);
- segmentLength = fullPath.Length - lastSeparator - 1;
+ // Get the full path
+ GetFullPathName(path, ref builder);
- if (foundTilde && segmentLength <= MaxShortName)
- possibleShortPath = true;
+ // If we have the exact same string we were passed in, don't allocate another string.
+ // TryExpandShortName does this input identity check.
+ string result = builder.AsSpan().Contains('~')
+ ? TryExpandShortFileName(ref builder, originalPath: path)
+ : builder.AsSpan().Equals(path.AsReadOnlySpan()) ? path : builder.ToString();
- // Check for a short filename path and try and expand it. Technically you don't need to have a tilde for a short name, but
- // this is how we've always done this. This expansion is costly so we'll continue to let other short paths slide.
- if (expandShortPaths && possibleShortPath)
- {
- return TryExpandShortFileName(ref fullPath, originalPath: path);
- }
- else
- {
- if (fullPath.Length == path.Length && fullPath.StartsWith(path))
- {
- // If we have the exact same string we were passed in, don't bother to allocate another string from the StringBuilder.
- return path;
- }
- else
- {
- return fullPath.ToString();
- }
- }
- }
- finally
- {
- // Clear the buffer
- fullPath.Free();
- }
+ // Clear the buffer
+ builder.Dispose();
+ return result;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsDosUnc(ref StringBuffer buffer)
- {
- return !PathInternal.IsDevice(ref buffer) && buffer.Length > 1 && buffer[0] == '\\' && buffer[1] == '\\';
- }
-
- private static unsafe void GetFullPathName(string path, ref StringBuffer fullPath)
+ private static void GetFullPathName(string path, ref ValueStringBuilder builder)
{
// If the string starts with an extended prefix we would need to remove it from the path before we call GetFullPathName as
// it doesn't root extended paths correctly. We don't currently resolve extended paths, so we'll just assert here.
Debug.Assert(PathInternal.IsPartiallyQualified(path) || !PathInternal.IsExtended(path));
- fixed (char* pathStart = path)
+ uint result = 0;
+ while ((result = Interop.Kernel32.GetFullPathNameW(path, (uint)builder.Capacity, ref builder.GetPinnableReference(), IntPtr.Zero)) > builder.Capacity)
{
- uint result = 0;
- while ((result = Interop.Kernel32.GetFullPathNameW(pathStart, (uint)fullPath.Capacity, fullPath.UnderlyingArray, IntPtr.Zero)) > fullPath.Capacity)
- {
- // Reported size is greater than the buffer size. Increase the capacity.
- fullPath.EnsureCapacity(checked((int)result));
- }
-
- if (result == 0)
- {
- // Failure, get the error and throw
- int errorCode = Marshal.GetLastWin32Error();
- if (errorCode == 0)
- errorCode = Interop.Errors.ERROR_BAD_PATHNAME;
- throw Win32Marshal.GetExceptionForWin32Error(errorCode, path);
- }
+ // Reported size is greater than the buffer size. Increase the capacity.
+ builder.EnsureCapacity(checked((int)result));
+ }
- fullPath.Length = checked((int)result);
+ if (result == 0)
+ {
+ // Failure, get the error and throw
+ int errorCode = Marshal.GetLastWin32Error();
+ if (errorCode == 0)
+ errorCode = Interop.Errors.ERROR_BAD_PATHNAME;
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, path);
}
+
+ builder.Length = (int)result;
}
- private static int GetInputBuffer(ref StringBuffer content, bool isDosUnc, ref StringBuffer buffer)
+ private static int PrependDevicePathChars(ref ValueStringBuilder content, bool isDosUnc, ref ValueStringBuilder buffer)
{
int length = content.Length;
@@ -209,37 +76,34 @@ namespace System.IO
: PathInternal.DevicePrefixLength;
buffer.EnsureCapacity(length + 1);
+ buffer.Length = 0;
if (isDosUnc)
{
- // Put the extended UNC prefix (\\?\UNC\) in front of the path
- buffer.CopyFrom(bufferIndex: 0, source: PathInternal.UncExtendedPathPrefix);
+ // Is a \\Server\Share, put \\?\UNC\ in the front
+ buffer.Append(PathInternal.UncExtendedPathPrefix);
- // Copy the source buffer over after the existing UNC prefix
- content.CopyTo(
- bufferIndex: PathInternal.UncPrefixLength,
- destination: ref buffer,
- destinationIndex: PathInternal.UncExtendedPrefixLength,
- count: content.Length - PathInternal.UncPrefixLength);
+ // Copy Server\Share\... over to the buffer
+ buffer.Append(content.AsSpan().Slice(PathInternal.UncPrefixLength));
// Return the prefix difference
return PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength;
}
else
{
- int prefixSize = PathInternal.ExtendedPathPrefix.Length;
- buffer.CopyFrom(bufferIndex: 0, source: PathInternal.ExtendedPathPrefix);
- content.CopyTo(bufferIndex: 0, destination: ref buffer, destinationIndex: prefixSize, count: content.Length);
- return prefixSize;
+ // Not an UNC, put the \\?\ prefix in front, then the original string
+ buffer.Append(PathInternal.ExtendedPathPrefix);
+ buffer.Append(content.AsSpan());
+ return PathInternal.DevicePrefixLength;
}
}
- private static string TryExpandShortFileName(ref StringBuffer outputBuffer, string originalPath)
+ private static string TryExpandShortFileName(ref ValueStringBuilder outputBuilder, string originalPath)
{
// We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To
// avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls.
- Debug.Assert(!PathInternal.IsPartiallyQualified(ref outputBuffer), "should have resolved by now");
+ Debug.Assert(!PathInternal.IsPartiallyQualified(outputBuilder.AsSpan()), "should have resolved by now");
// We'll have one of a few cases by now (the normalized path will have already:
//
@@ -247,135 +111,120 @@ namespace System.IO
// 2. Dos UNC (\\Server\Share)
// 3. Dos device path (\\.\C:\, \\?\C:\)
//
- // We want to put the extended syntax on the front if it doesn't already have it, which may mean switching from \\.\.
+ // We want to put the extended syntax on the front if it doesn't already have it (for long path support and speed), which may mean switching from \\.\.
//
// Note that we will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is).
- int rootLength = PathInternal.GetRootLength(ref outputBuffer);
- bool isDevice = PathInternal.IsDevice(ref outputBuffer);
+ int rootLength = PathInternal.GetRootLength(outputBuilder.AsSpan());
+ bool isDevice = PathInternal.IsDevice(outputBuilder.AsSpan());
- StringBuffer inputBuffer = new StringBuffer(0);
- try
- {
- bool isDosUnc = false;
- int rootDifference = 0;
- bool wasDotDevice = false;
+ // As this is a corner case we're not going to add a stackalloc here to keep the stack pressure down.
+ ValueStringBuilder inputBuilder = new ValueStringBuilder();
- // Add the extended prefix before expanding to allow growth over MAX_PATH
- if (isDevice)
- {
- // We have one of the following (\\?\ or \\.\)
- inputBuffer.Append(ref outputBuffer);
+ bool isDosUnc = false;
+ int rootDifference = 0;
+ bool wasDotDevice = false;
- if (outputBuffer[2] == '.')
- {
- wasDotDevice = true;
- inputBuffer[2] = '?';
- }
- }
- else
+ // Add the extended prefix before expanding to allow growth over MAX_PATH
+ if (isDevice)
+ {
+ // We have one of the following (\\?\ or \\.\)
+ inputBuilder.Append(outputBuilder.AsSpan());
+
+ if (outputBuilder[2] == '.')
{
- isDosUnc = IsDosUnc(ref outputBuffer);
- rootDifference = GetInputBuffer(ref outputBuffer, isDosUnc, ref inputBuffer);
+ wasDotDevice = true;
+ inputBuilder[2] = '?';
}
+ }
+ else
+ {
+ isDosUnc = !PathInternal.IsDevice(outputBuilder.AsSpan()) && outputBuilder.Length > 1 && outputBuilder[0] == '\\' && outputBuilder[1] == '\\';
+ rootDifference = PrependDevicePathChars(ref outputBuilder, isDosUnc, ref inputBuilder);
+ }
- rootLength += rootDifference;
- int inputLength = inputBuffer.Length;
+ rootLength += rootDifference;
+ int inputLength = inputBuilder.Length;
- bool success = false;
- int foundIndex = inputBuffer.Length - 1;
+ bool success = false;
+ int foundIndex = inputBuilder.Length - 1;
- while (!success)
- {
- uint result = Interop.Kernel32.GetLongPathNameW(inputBuffer.UnderlyingArray, outputBuffer.UnderlyingArray, (uint)outputBuffer.Capacity);
+ // Need to null terminate the input builder
+ inputBuilder.Append('\0');
- // Replace any temporary null we added
- if (inputBuffer[foundIndex] == '\0') inputBuffer[foundIndex] = '\\';
+ while (!success)
+ {
+ uint result = Interop.Kernel32.GetLongPathNameW(ref inputBuilder.GetPinnableReference(), ref outputBuilder.GetPinnableReference(), (uint)outputBuilder.Capacity);
- if (result == 0)
+ // Replace any temporary null we added
+ if (inputBuilder[foundIndex] == '\0') inputBuilder[foundIndex] = '\\';
+
+ if (result == 0)
+ {
+ // Look to see if we couldn't find the file
+ int error = Marshal.GetLastWin32Error();
+ if (error != Interop.Errors.ERROR_FILE_NOT_FOUND && error != Interop.Errors.ERROR_PATH_NOT_FOUND)
{
- // Look to see if we couldn't find the file
- int error = Marshal.GetLastWin32Error();
- if (error != Interop.Errors.ERROR_FILE_NOT_FOUND && error != Interop.Errors.ERROR_PATH_NOT_FOUND)
- {
- // Some other failure, give up
- break;
- }
-
- // We couldn't find the path at the given index, start looking further back in the string.
- foundIndex--;
-
- for (; foundIndex > rootLength && inputBuffer[foundIndex] != '\\'; foundIndex--) ;
- if (foundIndex == rootLength)
- {
- // Can't trim the path back any further
- break;
- }
- else
- {
- // Temporarily set a null in the string to get Windows to look further up the path
- inputBuffer[foundIndex] = '\0';
- }
+ // Some other failure, give up
+ break;
}
- else if (result > outputBuffer.Capacity)
+
+ // We couldn't find the path at the given index, start looking further back in the string.
+ foundIndex--;
+
+ for (; foundIndex > rootLength && inputBuilder[foundIndex] != '\\'; foundIndex--) ;
+ if (foundIndex == rootLength)
{
- // Not enough space. The result count for this API does not include the null terminator.
- outputBuffer.EnsureCapacity(checked((int)result));
- result = Interop.Kernel32.GetLongPathNameW(inputBuffer.UnderlyingArray, outputBuffer.UnderlyingArray, (uint)outputBuffer.Capacity);
+ // Can't trim the path back any further
+ break;
}
else
{
- // Found the path
- success = true;
- outputBuffer.Length = checked((int)result);
- if (foundIndex < inputLength - 1)
- {
- // It was a partial find, put the non-existent part of the path back
- outputBuffer.Append(ref inputBuffer, foundIndex, inputBuffer.Length - foundIndex);
- }
+ // Temporarily set a null in the string to get Windows to look further up the path
+ inputBuilder[foundIndex] = '\0';
}
}
-
- // Strip out the prefix and return the string
- ref StringBuffer bufferToUse = ref Choose(success, ref outputBuffer, ref inputBuffer);
-
- // Switch back from \\?\ to \\.\ if necessary
- if (wasDotDevice)
- bufferToUse[2] = '.';
-
- string returnValue = null;
-
- int newLength = (int)(bufferToUse.Length - rootDifference);
- if (isDosUnc)
- {
- // Need to go from \\?\UNC\ to \\?\UN\\
- bufferToUse[PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength] = '\\';
- }
-
- // We now need to strip out any added characters at the front of the string
- if (bufferToUse.SubstringEquals(originalPath, rootDifference, newLength))
+ else if (result > outputBuilder.Capacity)
{
- // Use the original path to avoid allocating
- returnValue = originalPath;
+ // Not enough space. The result count for this API does not include the null terminator.
+ outputBuilder.EnsureCapacity(checked((int)result));
+ result = Interop.Kernel32.GetLongPathNameW(ref inputBuilder.GetPinnableReference(), ref outputBuilder.GetPinnableReference(), (uint)outputBuilder.Capacity);
}
else
{
- returnValue = bufferToUse.Substring(rootDifference, newLength);
+ // Found the path
+ success = true;
+ outputBuilder.Length = checked((int)result);
+ if (foundIndex < inputLength - 1)
+ {
+ // It was a partial find, put the non-existent part of the path back
+ outputBuilder.Append(inputBuilder.AsSpan().Slice(foundIndex, inputBuilder.Length - foundIndex));
+ }
}
-
- return returnValue;
- }
- finally
- {
- inputBuffer.Free();
}
- }
- // Helper method to workaround lack of operator ? support for ref values
- private static ref StringBuffer Choose(bool condition, ref StringBuffer s1, ref StringBuffer s2)
- {
- if (condition) return ref s1;
- else return ref s2;
+ // Need to trim out the trailing separator in the input builder
+ inputBuilder.Length = inputBuilder.Length - 1;
+
+ // If we were able to expand the path, use it, otherwise use the original full path result
+ ref ValueStringBuilder builderToUse = ref (success ? ref outputBuilder : ref inputBuilder);
+
+ // Switch back from \\?\ to \\.\ if necessary
+ if (wasDotDevice)
+ builderToUse[2] = '.';
+
+ // Change from \\?\UNC\ to \\?\UN\\ if needed
+ if (isDosUnc)
+ builderToUse[PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength] = '\\';
+
+ // Strip out any added characters at the front of the string
+ ReadOnlySpan<char> output = builderToUse.AsSpan().Slice(rootDifference);
+
+ string returnValue = output.Equals(originalPath.AsReadOnlySpan())
+ ? originalPath : new string(output);
+
+ inputBuilder.Dispose();
+ return returnValue;
}
}
}
diff --git a/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.StringBuffer.cs b/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.StringBuffer.cs
deleted file mode 100644
index ea132423b..000000000
--- a/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.StringBuffer.cs
+++ /dev/null
@@ -1,91 +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.IO
-{
- /// <summary>Contains internal path helpers that are shared between many projects.</summary>
- internal static partial class PathInternal
- {
- /// <summary>
- /// Returns true if the path uses the extended syntax (\\?\)
- /// </summary>
- internal static bool IsExtended(ref StringBuffer path)
- {
- // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
- // Skipping of normalization will *only* occur if back slashes ('\') are used.
- return path.Length >= DevicePrefixLength
- && path[0] == '\\'
- && (path[1] == '\\' || path[1] == '?')
- && path[2] == '?'
- && path[3] == '\\';
- }
-
- /// <summary>
- /// Gets the length of the root of the path (drive, share, etc.).
- /// </summary>
- internal static int GetRootLength(ref StringBuffer path)
- {
- if (path.Length == 0)
- return 0;
-
- return GetRootLength(new ReadOnlySpan<char>(path.UnderlyingArray, 0, path.Length));
- }
-
- /// <summary>
- /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
- /// </summary>
- internal static bool IsDevice(ref StringBuffer path)
- {
- // If the path begins with any two separators is will be recognized and normalized and prepped with
- // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not.
- return IsExtended(ref path)
- ||
- (
- path.Length >= DevicePrefixLength
- && IsDirectorySeparator(path[0])
- && IsDirectorySeparator(path[1])
- && (path[2] == '.' || path[2] == '?')
- && IsDirectorySeparator(path[3])
- );
- }
-
- /// <summary>
- /// Returns true if the path specified is relative to the current drive or working directory.
- /// Returns false if the path is fixed to a specific drive or UNC path. This method does no
- /// validation of the path (URIs will be returned as relative as a result).
- /// </summary>
- /// <remarks>
- /// Handles paths that use the alternate directory separator. It is a frequent mistake to
- /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case.
- /// "C:a" is drive relative- meaning that it will be resolved against the current directory
- /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
- /// will not be used to modify the path).
- /// </remarks>
- internal static bool IsPartiallyQualified(ref StringBuffer path)
- {
- if (path.Length < 2)
- {
- // It isn't fixed, it must be relative. There is no way to specify a fixed
- // path with one character (or less).
- return true;
- }
-
- if (IsDirectorySeparator(path[0]))
- {
- // There is no valid way to specify a relative path with two initial slashes or
- // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
- return !(path[1] == '?' || IsDirectorySeparator(path[1]));
- }
-
- // The only way to specify a fixed path that doesn't begin with two slashes
- // is the drive, colon, slash format- i.e. C:\
- return !((path.Length >= 3)
- && (path[1] == VolumeSeparatorChar)
- && IsDirectorySeparator(path[2]));
- }
- }
-}
diff --git a/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs b/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs
index d4d5917bb..f931380c7 100644
--- a/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs
+++ b/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs
@@ -130,6 +130,19 @@ namespace System.IO
}
/// <summary>
+ /// Returns true if the path is a device UNC (\\?\UNC\, \\.\UNC\)
+ /// </summary>
+ internal static bool IsDeviceUNC(ReadOnlySpan<char> path)
+ {
+ return path.Length >= UncExtendedPrefixLength
+ && IsDevice(path)
+ && IsDirectorySeparator(path[7])
+ && path[4] == 'U'
+ && path[5] == 'N'
+ && path[6] == 'C';
+ }
+
+ /// <summary>
/// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the
/// path matches exactly (cannot use alternate directory separators) Windows will skip normalization
/// and path length checks.
@@ -177,30 +190,31 @@ namespace System.IO
int i = 0;
int volumeSeparatorLength = 2; // Length to the colon "C:"
int uncRootLength = 2; // Length to the start of the server name "\\"
+ int devicePrefixLength = PathInternal.ExtendedPathPrefix.Length;
- bool extendedSyntax = StartsWithOrdinal(path, ExtendedPathPrefix);
- bool extendedUncSyntax = StartsWithOrdinal(path, UncExtendedPathPrefix);
- if (extendedSyntax)
+ bool deviceSyntax = IsDevice(path);
+ bool deviceUnc = deviceSyntax && IsDeviceUNC(path);
+ if (deviceSyntax)
{
// Shift the position we look for the root from to account for the extended prefix
- if (extendedUncSyntax)
+ if (deviceUnc)
{
// "\\" -> "\\?\UNC\"
uncRootLength = UncExtendedPathPrefix.Length;
}
- else
+ else if (devicePrefixLength + 1 < pathLength && path[devicePrefixLength + 1] == VolumeSeparatorChar && IsValidDriveChar(path[devicePrefixLength]))
{
// "C:" -> "\\?\C:"
- volumeSeparatorLength += ExtendedPathPrefix.Length;
+ volumeSeparatorLength += devicePrefixLength;
}
}
- if ((!extendedSyntax || extendedUncSyntax) && pathLength > 0 && IsDirectorySeparator(path[0]))
+ if ((!deviceSyntax || deviceUnc) && pathLength > 0 && IsDirectorySeparator(path[0]))
{
// UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo")
i = 1; // Drive rooted (\foo) is one character
- if (extendedUncSyntax || (pathLength > 1 && IsDirectorySeparator(path[1])))
+ if (deviceUnc || (pathLength > 1 && IsDirectorySeparator(path[1])))
{
// UNC (\\?\UNC\ or \\), scan past the next two directory separators at most
// (e.g. to \\?\UNC\Server\Share or \\Server\Share\)
@@ -220,7 +234,18 @@ namespace System.IO
if (pathLength >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength]))
i++;
}
- return i;
+ else if (deviceSyntax && ((devicePrefixLength + 1 >= pathLength) || !(path[devicePrefixLength + 1] == VolumeSeparatorChar)))
+ {
+ i = devicePrefixLength;
+ int n = 1; // Maximum separators to skip
+ while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0))
+ i++;
+
+ if (i == devicePrefixLength)
+ i--;
+ }
+
+ return (i < pathLength && IsDirectorySeparator(path[i])) ? i + 1 : i;
}
private static bool StartsWithOrdinal(ReadOnlySpan<char> source, string value)
@@ -275,29 +300,6 @@ namespace System.IO
}
/// <summary>
- /// Returns the characters to skip at the start of the path if it starts with space(s) and a drive or directory separator.
- /// (examples are " C:", " \")
- /// This is a legacy behavior of Path.GetFullPath().
- /// </summary>
- /// <remarks>
- /// Note that this conflicts with IsPathRooted() which doesn't (and never did) such a skip.
- /// </remarks>
- internal static int PathStartSkip(ReadOnlySpan<char> path)
- {
- int startIndex = 0;
- while (startIndex < path.Length && path[startIndex] == ' ') startIndex++;
-
- if (startIndex > 0 && (startIndex < path.Length && IsDirectorySeparator(path[startIndex]))
- || (startIndex + 1 < path.Length && path[startIndex + 1] == ':' && IsValidDriveChar(path[startIndex])))
- {
- // Go ahead and skip spaces as we're either " C:" or " \"
- return startIndex;
- }
-
- return 0;
- }
-
- /// <summary>
/// True if the given character is a directory separator.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs b/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs
index 3eac1e74e..f2f350ddd 100644
--- a/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs
+++ b/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs
@@ -10,8 +10,12 @@ namespace System.IO
/// <summary>
/// Returns true if the path ends in a directory separator.
/// </summary>
- internal static bool EndsInDirectorySeparator(string path) =>
- !string.IsNullOrEmpty(path) && IsDirectorySeparator(path[path.Length - 1]);
+ internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) => path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]);
+
+ /// <summary>
+ /// Returns true if the path starts in a directory separator.
+ /// </summary>
+ internal static bool StartsWithDirectorySeparator(ReadOnlySpan<char> path) => path.Length > 0 && IsDirectorySeparator(path[0]);
/// <summary>
/// Get the common path length from the start of the string.
diff --git a/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs b/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs
index 5826f917b..6cdcd6952 100644
--- a/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs
+++ b/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs
@@ -432,7 +432,7 @@ namespace System.IO
{
if (value != null)
{
- WriteCore(value.AsReadOnlySpan(), _autoFlush);
+ WriteCore(value.AsSpan(), _autoFlush);
}
}
@@ -445,7 +445,7 @@ namespace System.IO
CheckAsyncTaskInProgress();
if (value != null)
{
- WriteCore(value.AsReadOnlySpan(), autoFlush: false);
+ WriteCore(value.AsSpan(), autoFlush: false);
}
WriteCore(new ReadOnlySpan<char>(CoreNewLine), autoFlush: true);
}
diff --git a/src/System.Private.CoreLib/shared/System/IO/Win32Marshal.cs b/src/System.Private.CoreLib/shared/System/IO/Win32Marshal.cs
index 14a064a70..0a8f2dca0 100644
--- a/src/System.Private.CoreLib/shared/System/IO/Win32Marshal.cs
+++ b/src/System.Private.CoreLib/shared/System/IO/Win32Marshal.cs
@@ -69,7 +69,7 @@ namespace System.IO
return new PathTooLongException(SR.Format(SR.IO_PathTooLong_Path, path));
case Interop.Errors.ERROR_INVALID_DRIVE:
- throw new DriveNotFoundException(SR.Format(SR.IO_DriveNotFound_Drive, path));
+ throw new DriveNotFoundException(SR.Format(SR.IO_DriveNotFound_Drive, path));
case Interop.Errors.ERROR_INVALID_PARAMETER:
return new IOException(Interop.Kernel32.GetMessage(errorCode), MakeHRFromErrorCode(errorCode));
diff --git a/src/System.Private.CoreLib/shared/System/Memory.cs b/src/System.Private.CoreLib/shared/System/Memory.cs
index 8fc5b04ee..4a1ce4dc9 100644
--- a/src/System.Private.CoreLib/shared/System/Memory.cs
+++ b/src/System.Private.CoreLib/shared/System/Memory.cs
@@ -58,6 +58,26 @@ namespace System
_length = array.Length;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Memory(T[] array, int start)
+ {
+ if (array == null)
+ {
+ if (start != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+ this = default;
+ return; // returns default
+ }
+ if (default(T) == null && array.GetType() != typeof(T[]))
+ ThrowHelper.ThrowArrayTypeMismatchException();
+ if ((uint)start > (uint)array.Length)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ _object = array;
+ _index = start;
+ _length = array.Length - start;
+ }
+
/// <summary>
/// Creates a new memory over the portion of the target array beginning
/// at 'start' index and ending at 'end' index (exclusive).
@@ -309,6 +329,16 @@ namespace System
return true;
}
+ if (_length == 0)
+ {
+#if FEATURE_PORTABLE_SPAN
+ arraySegment = new ArraySegment<T>(SpanHelpers.PerTypeValues<T>.EmptyArray);
+#else
+ arraySegment = ArraySegment<T>.Empty;
+#endif // FEATURE_PORTABLE_SPAN
+ return true;
+ }
+
arraySegment = default(ArraySegment<T>);
return false;
}
diff --git a/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs b/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs
new file mode 100644
index 000000000..4b330b7b1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs
@@ -0,0 +1,540 @@
+// 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.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using Internal.Runtime.CompilerServices;
+
+namespace System
+{
+ /// <summary>
+ /// Extension methods for Span{T}, Memory{T}, and friends.
+ /// </summary>
+ public static partial class MemoryExtensions
+ {
+ /// <summary>
+ /// Returns a value indicating whether the specified <paramref name="value"/> occurs within the <paramref name="span"/>.
+ /// <param name="span">The source span.</param>
+ /// <param name="value">The value to seek within the source span.</param>
+ /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
+ /// </summary>
+ public static bool Contains(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ return (IndexOf(span, value, comparisonType) >= 0);
+ }
+
+ /// <summary>
+ /// Determines whether this <paramref name="span"/> and the specified <paramref name="value"/> span have the same characters
+ /// when compared using the specified <paramref name="comparisonType"/> option.
+ /// <param name="span">The source span.</param>
+ /// <param name="value">The value to compare with the source span.</param>
+ /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
+ /// </summary>
+ public static bool Equals(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ StringSpanHelpers.CheckStringComparison(comparisonType);
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ return (CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.None) == 0);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return (CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.IgnoreCase) == 0);
+
+ case StringComparison.InvariantCulture:
+ return (CompareInfo.Invariant.Compare(span, value, CompareOptions.None) == 0);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return (CompareInfo.Invariant.Compare(span, value, CompareOptions.IgnoreCase) == 0);
+
+ case StringComparison.Ordinal:
+ if (span.Length != value.Length)
+ return false;
+ if (value.Length == 0) // span.Length == value.Length == 0
+ return true;
+ return span.SequenceEqual(value); //TODO: Optimize - https://github.com/dotnet/corefx/issues/27487
+
+ case StringComparison.OrdinalIgnoreCase:
+ if (span.Length != value.Length)
+ return false;
+ if (value.Length == 0) // span.Length == value.Length == 0
+ return true;
+ return (CompareInfo.CompareOrdinalIgnoreCase(span, value) == 0);
+ }
+
+ Debug.Fail("StringComparison outside range");
+ return false;
+ }
+
+ /// <summary>
+ /// Compares the specified <paramref name="span"/> and <paramref name="value"/> using the specified <paramref name="comparisonType"/>,
+ /// and returns an integer that indicates their relative position in the sort order.
+ /// <param name="span">The source span.</param>
+ /// <param name="value">The value to compare with the source span.</param>
+ /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
+ /// </summary>
+ public static int CompareTo(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ StringSpanHelpers.CheckStringComparison(comparisonType);
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ return CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.None);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.IgnoreCase);
+
+ case StringComparison.InvariantCulture:
+ return CompareInfo.Invariant.Compare(span, value, CompareOptions.None);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CompareInfo.Invariant.Compare(span, value, CompareOptions.IgnoreCase);
+
+ case StringComparison.Ordinal:
+ if (span.Length == 0 || value.Length == 0)
+ return span.Length - value.Length;
+ return string.CompareOrdinal(span, value);
+
+ case StringComparison.OrdinalIgnoreCase:
+ return CompareInfo.CompareOrdinalIgnoreCase(span, value);
+ }
+
+ Debug.Fail("StringComparison outside range");
+ return 0;
+ }
+
+ /// <summary>
+ /// Reports the zero-based index of the first occurrence of the specified <paramref name="value"/> in the current <paramref name="span"/>.
+ /// <param name="span">The source span.</param>
+ /// <param name="value">The value to seek within the source span.</param>
+ /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
+ /// </summary>
+ public static int IndexOf(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ StringSpanHelpers.CheckStringComparison(comparisonType);
+
+ if (value.Length == 0)
+ {
+ return 0;
+ }
+
+ if (span.Length == 0)
+ {
+ return -1;
+ }
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ return SpanHelpers.IndexOfCultureHelper(span, value, CultureInfo.CurrentCulture.CompareInfo);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return SpanHelpers.IndexOfCultureIgnoreCaseHelper(span, value, CultureInfo.CurrentCulture.CompareInfo);
+
+ case StringComparison.InvariantCulture:
+ return SpanHelpers.IndexOfCultureHelper(span, value, CompareInfo.Invariant);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return SpanHelpers.IndexOfCultureIgnoreCaseHelper(span, value, CompareInfo.Invariant);
+
+ case StringComparison.Ordinal:
+ return SpanHelpers.IndexOfOrdinalHelper(span, value, ignoreCase: false);
+
+ case StringComparison.OrdinalIgnoreCase:
+ return SpanHelpers.IndexOfOrdinalHelper(span, value, ignoreCase: true);
+ }
+
+ Debug.Fail("StringComparison outside range");
+ return -1;
+ }
+
+ /// <summary>
+ /// Copies the characters from the source span into the destination, converting each character to lowercase,
+ /// using the casing rules of the specified culture.
+ /// </summary>
+ /// <param name="source">The source span.</param>
+ /// <param name="destination">The destination span which contains the transformed characters.</param>
+ /// <param name="culture">An object that supplies culture-specific casing rules.</param>
+ /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.</remarks>
+ /// <exception cref="System.ArgumentNullException">
+ /// Thrown when <paramref name="culture"/> is null.
+ /// </exception>
+ public static int ToLower(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
+ {
+ if (culture == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture);
+
+ // Assuming that changing case does not affect length
+ if (destination.Length < source.Length)
+ return -1;
+
+ if (GlobalizationMode.Invariant)
+ culture.TextInfo.ToLowerAsciiInvariant(source, destination);
+ else
+ culture.TextInfo.ChangeCase(source, destination, toUpper: false);
+ return source.Length;
+ }
+
+ /// <summary>
+ /// Copies the characters from the source span into the destination, converting each character to lowercase,
+ /// using the casing rules of the invariant culture.
+ /// </summary>
+ /// <param name="source">The source span.</param>
+ /// <param name="destination">The destination span which contains the transformed characters.</param>
+ /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.</remarks>
+ public static int ToLowerInvariant(this ReadOnlySpan<char> source, Span<char> destination)
+ {
+ // Assuming that changing case does not affect length
+ if (destination.Length < source.Length)
+ return -1;
+
+ if (GlobalizationMode.Invariant)
+ CultureInfo.InvariantCulture.TextInfo.ToLowerAsciiInvariant(source, destination);
+ else
+ CultureInfo.InvariantCulture.TextInfo.ChangeCase(source, destination, toUpper: false);
+ return source.Length;
+ }
+
+ /// <summary>
+ /// Copies the characters from the source span into the destination, converting each character to uppercase,
+ /// using the casing rules of the specified culture.
+ /// </summary>
+ /// <param name="source">The source span.</param>
+ /// <param name="destination">The destination span which contains the transformed characters.</param>
+ /// <param name="culture">An object that supplies culture-specific casing rules.</param>
+ /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.</remarks>
+ /// <exception cref="System.ArgumentNullException">
+ /// Thrown when <paramref name="culture"/> is null.
+ /// </exception>
+ public static int ToUpper(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
+ {
+ if (culture == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture);
+
+ // Assuming that changing case does not affect length
+ if (destination.Length < source.Length)
+ return -1;
+
+ if (GlobalizationMode.Invariant)
+ culture.TextInfo.ToUpperAsciiInvariant(source, destination);
+ else
+ culture.TextInfo.ChangeCase(source, destination, toUpper: true);
+ return source.Length;
+ }
+
+ /// <summary>
+ /// Copies the characters from the source span into the destination, converting each character to uppercase
+ /// using the casing rules of the invariant culture.
+ /// </summary>
+ /// <param name="source">The source span.</param>
+ /// <param name="destination">The destination span which contains the transformed characters.</param>
+ /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.</remarks>
+ public static int ToUpperInvariant(this ReadOnlySpan<char> source, Span<char> destination)
+ {
+ // Assuming that changing case does not affect length
+ if (destination.Length < source.Length)
+ return -1;
+
+ if (GlobalizationMode.Invariant)
+ CultureInfo.InvariantCulture.TextInfo.ToUpperAsciiInvariant(source, destination);
+ else
+ CultureInfo.InvariantCulture.TextInfo.ChangeCase(source, destination, toUpper: true);
+ return source.Length;
+ }
+
+ /// <summary>
+ /// Determines whether the end of the <paramref name="span"/> matches the specified <paramref name="value"/> when compared using the specified <paramref name="comparisonType"/> option.
+ /// </summary>
+ /// <param name="span">The source span.</param>
+ /// <param name="value">The sequence to compare to the end of the source span.</param>
+ /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
+ public static bool EndsWith(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ if (value.Length == 0)
+ {
+ StringSpanHelpers.CheckStringComparison(comparisonType);
+ return true;
+ }
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ return SpanHelpers.EndsWithCultureHelper(span, value, CultureInfo.CurrentCulture.CompareInfo);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return SpanHelpers.EndsWithCultureIgnoreCaseHelper(span, value, CultureInfo.CurrentCulture.CompareInfo);
+
+ case StringComparison.InvariantCulture:
+ return SpanHelpers.EndsWithCultureHelper(span, value, CompareInfo.Invariant);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return SpanHelpers.EndsWithCultureIgnoreCaseHelper(span, value, CompareInfo.Invariant);
+
+ case StringComparison.Ordinal:
+ return span.EndsWith(value); //TODO: Optimize - https://github.com/dotnet/corefx/issues/27487
+
+ case StringComparison.OrdinalIgnoreCase:
+ return SpanHelpers.EndsWithOrdinalIgnoreCaseHelper(span, value);
+
+ default:
+ throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
+ }
+ }
+
+ /// <summary>
+ /// Determines whether the beginning of the <paramref name="span"/> matches the specified <paramref name="value"/> when compared using the specified <paramref name="comparisonType"/> option.
+ /// </summary>
+ /// <param name="span">The source span.</param>
+ /// <param name="value">The sequence to compare to the beginning of the source span.</param>
+ /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
+ public static bool StartsWith(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ if (value.Length == 0)
+ {
+ StringSpanHelpers.CheckStringComparison(comparisonType);
+ return true;
+ }
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ return SpanHelpers.StartsWithCultureHelper(span, value, CultureInfo.CurrentCulture.CompareInfo);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return SpanHelpers.StartsWithCultureIgnoreCaseHelper(span, value, CultureInfo.CurrentCulture.CompareInfo);
+
+ case StringComparison.InvariantCulture:
+ return SpanHelpers.StartsWithCultureHelper(span, value, CompareInfo.Invariant);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return SpanHelpers.StartsWithCultureIgnoreCaseHelper(span, value, CompareInfo.Invariant);
+
+ case StringComparison.Ordinal:
+ return span.StartsWith(value); //TODO: Optimize - https://github.com/dotnet/corefx/issues/27487
+
+ case StringComparison.OrdinalIgnoreCase:
+ return SpanHelpers.StartsWithOrdinalIgnoreCaseHelper(span, value);
+
+ default:
+ throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
+ }
+ }
+
+ /// <summary>
+ /// Casts a Span of one primitive type <typeparamref name="T"/> to Span of bytes.
+ /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
+ /// </summary>
+ /// <param name="source">The source slice, of type <typeparamref name="T"/>.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when <typeparamref name="T"/> contains pointers.
+ /// </exception>
+ /// <exception cref="System.OverflowException">
+ /// Thrown if the Length property of the new Span would exceed Int32.MaxValue.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<byte> AsBytes<T>(this Span<T> source)
+ where T : struct
+ {
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
+
+ return new Span<byte>(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(source)),
+ checked(source.Length * Unsafe.SizeOf<T>()));
+ }
+
+ /// <summary>
+ /// Casts a ReadOnlySpan of one primitive type <typeparamref name="T"/> to ReadOnlySpan of bytes.
+ /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
+ /// </summary>
+ /// <param name="source">The source slice, of type <typeparamref name="T"/>.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when <typeparamref name="T"/> contains pointers.
+ /// </exception>
+ /// <exception cref="System.OverflowException">
+ /// Thrown if the Length property of the new Span would exceed Int32.MaxValue.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<byte> AsBytes<T>(this ReadOnlySpan<T> source)
+ where T : struct
+ {
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
+
+ return new ReadOnlySpan<byte>(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(source)),
+ checked(source.Length * Unsafe.SizeOf<T>()));
+ }
+
+ /// <summary>
+ /// Creates a new span over the portion of the target array.
+ /// </summary>
+ public static Span<T> AsSpan<T>(this T[] array, int start)
+ {
+ if (array == null)
+ {
+ if (start != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+ return default;
+ }
+ if (default(T) == null && array.GetType() != typeof(T[]))
+ ThrowHelper.ThrowArrayTypeMismatchException();
+ if ((uint)start > (uint)array.Length)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ return new Span<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start), array.Length - start);
+ }
+
+ /// <summary>
+ /// Creates a new readonly span over the portion of the target string.
+ /// </summary>
+ /// <param name="text">The target string.</param>
+ /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
+ public static ReadOnlySpan<char> AsSpan(this string text)
+ {
+ if (text == null)
+ return default;
+
+ return new ReadOnlySpan<char>(ref text.GetRawStringData(), text.Length);
+ }
+
+ /// <summary>
+ /// Creates a new readonly span over the portion of the target string.
+ /// </summary>
+ /// <param name="text">The target string.</param>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="text"/> is null.</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;text.Length).
+ /// </exception>
+ public static ReadOnlySpan<char> AsSpan(this string text, int start)
+ {
+ if (text == null)
+ {
+ if (start != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+ return default;
+ }
+
+ if ((uint)start > (uint)text.Length)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), start), text.Length - start);
+ }
+
+ /// <summary>
+ /// Creates a new readonly span over the portion of the target string.
+ /// </summary>
+ /// <param name="text">The target string.</param>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <param name="length">The desired length for the slice (exclusive).</param>
+ /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> index or <paramref name="length"/> is not in range.
+ /// </exception>
+ public static ReadOnlySpan<char> AsSpan(this string text, int start, int length)
+ {
+ if (text == null)
+ {
+ if (start != 0 || length != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+ return default;
+ }
+
+ if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), start), length);
+ }
+
+ /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
+ /// <param name="text">The target string.</param>
+ /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
+ public static ReadOnlyMemory<char> AsMemory(this string text)
+ {
+ if (text == null)
+ return default;
+
+ return new ReadOnlyMemory<char>(text, 0, text.Length);
+ }
+
+ /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
+ /// <param name="text">The target string.</param>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;text.Length).
+ /// </exception>
+ public static ReadOnlyMemory<char> AsMemory(this string text, int start)
+ {
+ if (text == null)
+ {
+ if (start != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+ return default;
+ }
+
+ if ((uint)start > (uint)text.Length)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ return new ReadOnlyMemory<char>(text, start, text.Length - start);
+ }
+
+ /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
+ /// <param name="text">The target string.</param>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <param name="length">The desired length for the slice (exclusive).</param>
+ /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> index or <paramref name="length"/> is not in range.
+ /// </exception>
+ public static ReadOnlyMemory<char> AsMemory(this string text, int start, int length)
+ {
+ if (text == null)
+ {
+ if (start != 0 || length != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+ return default;
+ }
+
+ if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ return new ReadOnlyMemory<char>(text, start, length);
+ }
+
+ /// <summary>Attempts to get the underlying <see cref="string"/> from a <see cref="ReadOnlyMemory{T}"/>.</summary>
+ /// <param name="readOnlyMemory">The memory that may be wrapping a <see cref="string"/> object.</param>
+ /// <param name="text">The string.</param>
+ /// <param name="start">The starting location in <paramref name="text"/>.</param>
+ /// <param name="length">The number of items in <paramref name="text"/>.</param>
+ /// <returns></returns>
+ public static bool TryGetString(this ReadOnlyMemory<char> readOnlyMemory, out string text, out int start, out int length)
+ {
+ if (readOnlyMemory.GetObjectStartLength(out int offset, out int count) is string s)
+ {
+ text = s;
+ start = offset;
+ length = count;
+ return true;
+ }
+ else
+ {
+ text = null;
+ start = 0;
+ length = 0;
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs b/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs
new file mode 100644
index 000000000..effdecf92
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs
@@ -0,0 +1,1170 @@
+// 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.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+#if !netstandard
+using Internal.Runtime.CompilerServices;
+#endif
+
+namespace System
+{
+ /// <summary>
+ /// Extension methods for Span{T}, Memory{T}, and friends.
+ /// </summary>
+ public static partial class MemoryExtensions
+ {
+ /// <summary>
+ /// Removes all leading and trailing white-space characters from the span.
+ /// </summary>
+ public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span)
+ {
+ return span.TrimStart().TrimEnd();
+ }
+
+ /// <summary>
+ /// Removes all leading white-space characters from the span.
+ /// </summary>
+ public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span)
+ {
+ int start = 0;
+ for (; start < span.Length; start++)
+ {
+ if (!char.IsWhiteSpace(span[start]))
+ break;
+ }
+ return span.Slice(start);
+ }
+
+ /// <summary>
+ /// Removes all trailing white-space characters from the span.
+ /// </summary>
+ public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span)
+ {
+ int end = span.Length - 1;
+ for (; end >= 0; end--)
+ {
+ if (!char.IsWhiteSpace(span[end]))
+ break;
+ }
+ return span.Slice(0, end + 1);
+ }
+
+ /// <summary>
+ /// Removes all leading and trailing occurrences of a specified character.
+ /// </summary>
+ /// <param name="span">The source span from which the character is removed.</param>
+ /// <param name="trimChar">The specified character to look for and remove.</param>
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span, char trimChar)
+ {
+ return span.TrimStart(trimChar).TrimEnd(trimChar);
+ }
+
+ /// <summary>
+ /// Removes all leading occurrences of a specified character.
+ /// </summary>
+ /// <param name="span">The source span from which the character is removed.</param>
+ /// <param name="trimChar">The specified character to look for and remove.</param>
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span, char trimChar)
+ {
+ int start = 0;
+ for (; start < span.Length; start++)
+ {
+ if (span[start] != trimChar)
+ break;
+ }
+ return span.Slice(start);
+ }
+
+ /// <summary>
+ /// Removes all trailing occurrences of a specified character.
+ /// </summary>
+ /// <param name="span">The source span from which the character is removed.</param>
+ /// <param name="trimChar">The specified character to look for and remove.</param>
+ public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span, char trimChar)
+ {
+ int end = span.Length - 1;
+ for (; end >= 0; end--)
+ {
+ if (span[end] != trimChar)
+ break;
+ }
+ return span.Slice(0, end + 1);
+ }
+
+ /// <summary>
+ /// Removes all leading and trailing occurrences of a set of characters specified
+ /// in a readonly span from the span.
+ /// </summary>
+ /// <param name="span">The source span from which the characters are removed.</param>
+ /// <param name="trimChars">The span which contains the set of characters to remove.</param>
+ public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
+ {
+ return span.TrimStart(trimChars).TrimEnd(trimChars);
+ }
+
+ /// <summary>
+ /// Removes all leading occurrences of a set of characters specified
+ /// in a readonly span from the span.
+ /// </summary>
+ /// <param name="span">The source span from which the characters are removed.</param>
+ /// <param name="trimChars">The span which contains the set of characters to remove.</param>
+ public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
+ {
+ int start = 0;
+ for (; start < span.Length; start++)
+ {
+ for (int i = 0; i < trimChars.Length; i++)
+ {
+ if (span[start] == trimChars[i])
+ goto Next;
+ }
+ break;
+ Next:
+ ;
+ }
+ return span.Slice(start);
+ }
+
+ /// <summary>
+ /// Removes all trailing occurrences of a set of characters specified
+ /// in a readonly span from the span.
+ /// </summary>
+ /// <param name="span">The source span from which the characters are removed.</param>
+ /// <param name="trimChars">The span which contains the set of characters to remove.</param>
+ public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
+ {
+ int end = span.Length - 1;
+ for (; end >= 0; end--)
+ {
+ for (int i = 0; i < trimChars.Length; i++)
+ {
+ if (span[end] == trimChars[i])
+ goto Next;
+ }
+ break;
+ Next:
+ ;
+ }
+ return span.Slice(0, end + 1);
+ }
+
+ /// <summary>
+ /// Indicates whether the specified span contains only white-space characters.
+ /// </summary>
+ public static bool IsWhiteSpace(this ReadOnlySpan<char> span)
+ {
+ for (int i = 0; i < span.Length; i++)
+ {
+ if (!char.IsWhiteSpace(span[i]))
+ return false;
+ }
+ return true;
+ }
+
+ /// <summary>
+ /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The value to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOf<T>(this Span<T> span, T value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value),
+ span.Length);
+ return SpanHelpers.IndexOf<T>(ref MemoryMarshal.GetReference(span), value, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the specified sequence and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The sequence to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOf<T>(this Span<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ value.Length);
+ return SpanHelpers.IndexOf<T>(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length);
+ }
+
+ /// <summary>
+ /// Searches for the specified value and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The value to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOf<T>(this Span<T> span, T value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value),
+ span.Length);
+ return SpanHelpers.LastIndexOf<T>(ref MemoryMarshal.GetReference(span), value, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the specified sequence and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The sequence to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOf<T>(this Span<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ value.Length);
+ return SpanHelpers.LastIndexOf<T>(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length);
+ }
+
+ /// <summary>
+ /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T).
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool SequenceEqual<T>(this Span<T> first, ReadOnlySpan<T> second)
+ where T : IEquatable<T>
+ {
+ int length = first.Length;
+ if (typeof(T) == typeof(byte))
+ return length == second.Length &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(first)),
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(second)),
+ length);
+ return length == second.Length && SpanHelpers.SequenceEqual(ref MemoryMarshal.GetReference(first), ref MemoryMarshal.GetReference(second), length);
+ }
+
+ /// <summary>
+ /// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T).
+ /// </summary>
+ public static int SequenceCompareTo<T>(this Span<T> first, ReadOnlySpan<T> second)
+ where T : IComparable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.SequenceCompareTo(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(first)),
+ first.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(second)),
+ second.Length);
+ return SpanHelpers.SequenceCompareTo(ref MemoryMarshal.GetReference(first), first.Length, ref MemoryMarshal.GetReference(second), second.Length);
+ }
+
+ /// <summary>
+ /// Reverses the sequence of the elements in the entire span.
+ /// </summary>
+ public static void Reverse<T>(this Span<T> span)
+ {
+ ref T p = ref MemoryMarshal.GetReference(span);
+ int i = 0;
+ int j = span.Length - 1;
+ while (i < j)
+ {
+ T temp = Unsafe.Add(ref p, i);
+ Unsafe.Add(ref p, i) = Unsafe.Add(ref p, j);
+ Unsafe.Add(ref p, j) = temp;
+ i++;
+ j--;
+ }
+ }
+
+ /// <summary>
+ /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The value to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOf<T>(this ReadOnlySpan<T> span, T value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value),
+ span.Length);
+ return SpanHelpers.IndexOf<T>(ref MemoryMarshal.GetReference(span), value, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the specified sequence and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The sequence to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOf<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ value.Length);
+ return SpanHelpers.IndexOf<T>(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length);
+ }
+
+ /// <summary>
+ /// Searches for the specified value and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The value to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOf<T>(this ReadOnlySpan<T> span, T value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value),
+ span.Length);
+ return SpanHelpers.LastIndexOf<T>(ref MemoryMarshal.GetReference(span), value, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the specified sequence and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The sequence to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOf<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ value.Length);
+ return SpanHelpers.LastIndexOf<T>(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length);
+ }
+
+ /// <summary>
+ /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOfAny<T>(this Span<T> span, T value0, T value1)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ span.Length);
+
+ return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ /// <param name="value2">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOfAny<T>(this Span<T> span, T value0, T value1, T value2)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ Unsafe.As<T, byte>(ref value2),
+ span.Length);
+
+ return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="values">The set of values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOfAny<T>(this Span<T> span, ReadOnlySpan<T> values)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(values)),
+ values.Length);
+
+ return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(values), values.Length);
+ }
+
+ /// <summary>
+ /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOfAny<T>(this ReadOnlySpan<T> span, T value0, T value1)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ span.Length);
+
+ return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ /// <param name="value2">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOfAny<T>(this ReadOnlySpan<T> span, T value0, T value1, T value2)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ Unsafe.As<T, byte>(ref value2),
+ span.Length);
+
+ return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="values">The set of values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOfAny<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> values)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(values)),
+ values.Length);
+
+ return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(values), values.Length);
+ }
+
+ /// <summary>
+ /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOfAny<T>(this Span<T> span, T value0, T value1)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ span.Length);
+ return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ /// <param name="value2">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOfAny<T>(this Span<T> span, T value0, T value1, T value2)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ Unsafe.As<T, byte>(ref value2),
+ span.Length);
+ return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="values">The set of values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOfAny<T>(this Span<T> span, ReadOnlySpan<T> values)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(values)),
+ values.Length);
+ return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(values), values.Length);
+ }
+
+ /// <summary>
+ /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOfAny<T>(this ReadOnlySpan<T> span, T value0, T value1)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ span.Length);
+ return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ /// <param name="value2">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOfAny<T>(this ReadOnlySpan<T> span, T value0, T value1, T value2)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ Unsafe.As<T, byte>(ref value2),
+ span.Length);
+ return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="values">The set of values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOfAny<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> values)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(values)),
+ values.Length);
+ return SpanHelpers.LastIndexOfAny<T>(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(values), values.Length);
+ }
+
+ /// <summary>
+ /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T).
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool SequenceEqual<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T> second)
+ where T : IEquatable<T>
+ {
+ int length = first.Length;
+ if (typeof(T) == typeof(byte))
+ return length == second.Length &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(first)),
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(second)),
+ length);
+ return length == second.Length && SpanHelpers.SequenceEqual(ref MemoryMarshal.GetReference(first), ref MemoryMarshal.GetReference(second), length);
+ }
+
+ /// <summary>
+ /// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T).
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int SequenceCompareTo<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T> second)
+ where T : IComparable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.SequenceCompareTo(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(first)),
+ first.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(second)),
+ second.Length);
+ return SpanHelpers.SequenceCompareTo(ref MemoryMarshal.GetReference(first), first.Length, ref MemoryMarshal.GetReference(second), second.Length);
+ }
+
+ /// <summary>
+ /// Determines whether the specified sequence appears at the start of the span.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool StartsWith<T>(this Span<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ int valueLength = value.Length;
+ if (typeof(T) == typeof(byte))
+ return valueLength <= span.Length &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ valueLength);
+ return valueLength <= span.Length && SpanHelpers.SequenceEqual(ref MemoryMarshal.GetReference(span), ref MemoryMarshal.GetReference(value), valueLength);
+ }
+
+ /// <summary>
+ /// Determines whether the specified sequence appears at the start of the span.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool StartsWith<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ int valueLength = value.Length;
+ if (typeof(T) == typeof(byte))
+ return valueLength <= span.Length &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ valueLength);
+ return valueLength <= span.Length && SpanHelpers.SequenceEqual(ref MemoryMarshal.GetReference(span), ref MemoryMarshal.GetReference(value), valueLength);
+ }
+
+ /// <summary>
+ /// Determines whether the specified sequence appears at the end of the span.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool EndsWith<T>(this Span<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ int spanLength = span.Length;
+ int valueLength = value.Length;
+ if (typeof(T) == typeof(byte))
+ return valueLength <= spanLength &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.As<T, byte>(ref Unsafe.Add(ref MemoryMarshal.GetReference(span), spanLength - valueLength)),
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ valueLength);
+ return valueLength <= spanLength &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.Add(ref MemoryMarshal.GetReference(span), spanLength - valueLength),
+ ref MemoryMarshal.GetReference(value),
+ valueLength);
+ }
+
+ /// <summary>
+ /// Determines whether the specified sequence appears at the end of the span.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool EndsWith<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ int spanLength = span.Length;
+ int valueLength = value.Length;
+ if (typeof(T) == typeof(byte))
+ return valueLength <= spanLength &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.As<T, byte>(ref Unsafe.Add(ref MemoryMarshal.GetReference(span), spanLength - valueLength)),
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ valueLength);
+ return valueLength <= spanLength &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.Add(ref MemoryMarshal.GetReference(span), spanLength - valueLength),
+ ref MemoryMarshal.GetReference(value),
+ valueLength);
+ }
+
+ /// <summary>
+ /// Creates a new span over the portion of the target array.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> AsSpan<T>(this T[] array)
+ {
+ return new Span<T>(array);
+ }
+
+ /// <summary>
+ /// Creates a new span over the portion of the target array segment.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> AsSpan<T>(this ArraySegment<T> arraySegment)
+ {
+ return new Span<T>(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
+ }
+
+ /// <summary>
+ /// Creates a new readonly span over the entire target array.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<T> AsReadOnlySpan<T>(this T[] array)
+ {
+ return new ReadOnlySpan<T>(array);
+ }
+
+ /// <summary>
+ /// Creates a new readonly span over the entire target span.
+ /// </summary>
+ public static ReadOnlySpan<T> AsReadOnlySpan<T>(this Span<T> span) => span;
+
+ /// <summary>
+ /// Creates a new readonly span over the target array segment.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<T> AsReadOnlySpan<T>(this ArraySegment<T> arraySegment)
+ {
+ return new ReadOnlySpan<T>(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
+ }
+
+ /// <summary>
+ /// Creates a new readonly memory over the entire target memory.
+ /// </summary>
+ public static ReadOnlyMemory<T> AsReadOnlyMemory<T>(this Memory<T> memory) => memory;
+
+ /// <summary>
+ /// Creates a new memory over the portion of the target array.
+ /// </summary>
+ public static Memory<T> AsMemory<T>(this T[] array, int start) => new Memory<T>(array, start);
+
+ /// <summary>
+ /// Copies the contents of the array into the span. If the source
+ /// and destinations overlap, this method behaves as if the original values in
+ /// a temporary location before the destination is overwritten.
+ ///
+ ///<param name="array">The array to copy items from.</param>
+ /// <param name="destination">The span to copy items into.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when the destination Span is shorter than the source array.
+ /// </exception>
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyTo<T>(this T[] array, Span<T> destination)
+ {
+ new ReadOnlySpan<T>(array).CopyTo(destination);
+ }
+
+ /// <summary>
+ /// Copies the contents of the array into the memory. If the source
+ /// and destinations overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.
+ ///
+ ///<param name="array">The array to copy items from.</param>
+ /// <param name="destination">The memory to copy items into.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when the destination is shorter than the source array.
+ /// </exception>
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyTo<T>(this T[] array, Memory<T> destination)
+ {
+ array.CopyTo(destination.Span);
+ }
+
+ //
+ // Overlaps
+ // ========
+ //
+ // The following methods can be used to determine if two sequences
+ // overlap in memory.
+ //
+ // Two sequences overlap if they have positions in common and neither
+ // is empty. Empty sequences do not overlap with any other sequence.
+ //
+ // If two sequences overlap, the element offset is the number of
+ // elements by which the second sequence is offset from the first
+ // sequence (i.e., second minus first). An exception is thrown if the
+ // number is not a whole number, which can happen when a sequence of a
+ // smaller type is cast to a sequence of a larger type with unsafe code
+ // or NonPortableCast. If the sequences do not overlap, the offset is
+ // meaningless and arbitrarily set to zero.
+ //
+ // Implementation
+ // --------------
+ //
+ // Implementing this correctly is quite tricky due of two problems:
+ //
+ // * If the sequences refer to two different objects on the managed
+ // heap, the garbage collector can move them freely around or change
+ // their relative order in memory.
+ //
+ // * The distance between two sequences can be greater than
+ // int.MaxValue (on a 32-bit system) or long.MaxValue (on a 64-bit
+ // system).
+ //
+ // (For simplicity, the following text assumes a 32-bit system, but
+ // everything also applies to a 64-bit system if every 32 is replaced a
+ // 64.)
+ //
+ // The first problem is solved by calculating the distance with exactly
+ // one atomic operation. If the garbage collector happens to move the
+ // sequences afterwards and the sequences overlapped before, they will
+ // still overlap after the move and their distance hasn't changed. If
+ // the sequences did not overlap, the distance can change but the
+ // sequences still won't overlap.
+ //
+ // The second problem is solved by making all addresses relative to the
+ // start of the first sequence and performing all operations in
+ // unsigned integer arithmetic modulo 2³².
+ //
+ // Example
+ // -------
+ //
+ // Let's say there are two sequences, x and y. Let
+ //
+ // ref T xRef = MemoryMarshal.GetReference(x)
+ // uint xLength = x.Length * Unsafe.SizeOf<T>()
+ // ref T yRef = MemoryMarshal.GetReference(y)
+ // uint yLength = y.Length * Unsafe.SizeOf<T>()
+ //
+ // Visually, the two sequences are located somewhere in the 32-bit
+ // address space as follows:
+ //
+ // [----------------------------------------------) normal address space
+ // 0 2³²
+ // [------------------) first sequence
+ // xRef xRef + xLength
+ // [--------------------------) . second sequence
+ // yRef . yRef + yLength
+ // : . . .
+ // : . . .
+ // . . .
+ // . . .
+ // . . .
+ // [----------------------------------------------) relative address space
+ // 0 . . 2³²
+ // [------------------) : first sequence
+ // x1 . x2 :
+ // -------------) [------------- second sequence
+ // y2 y1
+ //
+ // The idea is to make all addresses relative to xRef: Let x1 be the
+ // start address of x in this relative address space, x2 the end
+ // address of x, y1 the start address of y, and y2 the end address of
+ // y:
+ //
+ // nuint x1 = 0
+ // nuint x2 = xLength
+ // nuint y1 = (nuint)Unsafe.ByteOffset(xRef, yRef)
+ // nuint y2 = y1 + yLength
+ //
+ // xRef relative to xRef is 0.
+ //
+ // x2 is simply x1 + xLength. This cannot overflow.
+ //
+ // yRef relative to xRef is (yRef - xRef). If (yRef - xRef) is
+ // negative, casting it to an unsigned 32-bit integer turns it into
+ // (yRef - xRef + 2³²). So, in the example above, y1 moves to the right
+ // of x2.
+ //
+ // y2 is simply y1 + yLength. Note that this can overflow, as in the
+ // example above, which must be avoided.
+ //
+ // The two sequences do *not* overlap if y is entirely in the space
+ // right of x in the relative address space. (It can't be left of it!)
+ //
+ // (y1 >= x2) && (y2 <= 2³²)
+ //
+ // Inversely, they do overlap if
+ //
+ // (y1 < x2) || (y2 > 2³²)
+ //
+ // After substituting x2 and y2 with their respective definition:
+ //
+ // == (y1 < xLength) || (y1 + yLength > 2³²)
+ //
+ // Since yLength can't be greater than the size of the address space,
+ // the overflow can be avoided as follows:
+ //
+ // == (y1 < xLength) || (y1 > 2³² - yLength)
+ //
+ // However, 2³² cannot be stored in an unsigned 32-bit integer, so one
+ // more change is needed to keep doing everything with unsigned 32-bit
+ // integers:
+ //
+ // == (y1 < xLength) || (y1 > -yLength)
+ //
+ // Due to modulo arithmetic, this gives exactly same result *except* if
+ // yLength is zero, since 2³² - 0 is 0 and not 2³². So the case
+ // y.IsEmpty must be handled separately first.
+ //
+
+ /// <summary>
+ /// Determines whether two sequences overlap in memory.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool Overlaps<T>(this Span<T> first, ReadOnlySpan<T> second)
+ {
+ return Overlaps((ReadOnlySpan<T>)first, second);
+ }
+
+ /// <summary>
+ /// Determines whether two sequences overlap in memory and outputs the element offset.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool Overlaps<T>(this Span<T> first, ReadOnlySpan<T> second, out int elementOffset)
+ {
+ return Overlaps((ReadOnlySpan<T>)first, second, out elementOffset);
+ }
+
+ /// <summary>
+ /// Determines whether two sequences overlap in memory.
+ /// </summary>
+ public static bool Overlaps<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T> second)
+ {
+ if (first.IsEmpty || second.IsEmpty)
+ {
+ return false;
+ }
+
+ IntPtr byteOffset = Unsafe.ByteOffset(
+ ref MemoryMarshal.GetReference(first),
+ ref MemoryMarshal.GetReference(second));
+
+ if (Unsafe.SizeOf<IntPtr>() == sizeof(int))
+ {
+ return (uint)byteOffset < (uint)(first.Length * Unsafe.SizeOf<T>()) ||
+ (uint)byteOffset > (uint)-(second.Length * Unsafe.SizeOf<T>());
+ }
+ else
+ {
+ return (ulong)byteOffset < (ulong)((long)first.Length * Unsafe.SizeOf<T>()) ||
+ (ulong)byteOffset > (ulong)-((long)second.Length * Unsafe.SizeOf<T>());
+ }
+ }
+
+ /// <summary>
+ /// Determines whether two sequences overlap in memory and outputs the element offset.
+ /// </summary>
+ public static bool Overlaps<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T> second, out int elementOffset)
+ {
+ if (first.IsEmpty || second.IsEmpty)
+ {
+ elementOffset = 0;
+ return false;
+ }
+
+ IntPtr byteOffset = Unsafe.ByteOffset(
+ ref MemoryMarshal.GetReference(first),
+ ref MemoryMarshal.GetReference(second));
+
+ if (Unsafe.SizeOf<IntPtr>() == sizeof(int))
+ {
+ if ((uint)byteOffset < (uint)(first.Length * Unsafe.SizeOf<T>()) ||
+ (uint)byteOffset > (uint)-(second.Length * Unsafe.SizeOf<T>()))
+ {
+ if ((int)byteOffset % Unsafe.SizeOf<T>() != 0)
+ ThrowHelper.ThrowArgumentException_OverlapAlignmentMismatch();
+
+ elementOffset = (int)byteOffset / Unsafe.SizeOf<T>();
+ return true;
+ }
+ else
+ {
+ elementOffset = 0;
+ return false;
+ }
+ }
+ else
+ {
+ if ((ulong)byteOffset < (ulong)((long)first.Length * Unsafe.SizeOf<T>()) ||
+ (ulong)byteOffset > (ulong)-((long)second.Length * Unsafe.SizeOf<T>()))
+ {
+ if ((long)byteOffset % Unsafe.SizeOf<T>() != 0)
+ ThrowHelper.ThrowArgumentException_OverlapAlignmentMismatch();
+
+ elementOffset = (int)((long)byteOffset / Unsafe.SizeOf<T>());
+ return true;
+ }
+ else
+ {
+ elementOffset = 0;
+ return false;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Searches an entire sorted <see cref="Span{T}"/> for a value
+ /// using the specified <see cref="IComparable{T}"/> generic interface.
+ /// </summary>
+ /// <typeparam name="T">The element type of the span.</typeparam>
+ /// <param name="span">The sorted <see cref="Span{T}"/> to search.</param>
+ /// <param name="comparable">The <see cref="IComparable{T}"/> to use when comparing.</param>
+ /// <returns>
+ /// The zero-based index of <paramref name="comparable"/> in the sorted <paramref name="span"/>,
+ /// if <paramref name="comparable"/> is found; otherwise, a negative number that is the bitwise complement
+ /// of the index of the next element that is larger than <paramref name="comparable"/> or, if there is
+ /// no larger element, the bitwise complement of <see cref="Span{T}.Length"/>.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// <paramref name = "comparable" /> is <see langword="null"/> .
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T>(
+ this Span<T> span, IComparable<T> comparable)
+ {
+ return BinarySearch<T, IComparable<T>>(span, comparable);
+ }
+
+ /// <summary>
+ /// Searches an entire sorted <see cref="Span{T}"/> for a value
+ /// using the specified <typeparamref name="TComparable"/> generic type.
+ /// </summary>
+ /// <typeparam name="T">The element type of the span.</typeparam>
+ /// <typeparam name="TComparable">The specific type of <see cref="IComparable{T}"/>.</typeparam>
+ /// <param name="span">The sorted <see cref="Span{T}"/> to search.</param>
+ /// <param name="comparable">The <typeparamref name="TComparable"/> to use when comparing.</param>
+ /// <returns>
+ /// The zero-based index of <paramref name="comparable"/> in the sorted <paramref name="span"/>,
+ /// if <paramref name="comparable"/> is found; otherwise, a negative number that is the bitwise complement
+ /// of the index of the next element that is larger than <paramref name="comparable"/> or, if there is
+ /// no larger element, the bitwise complement of <see cref="Span{T}.Length"/>.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// <paramref name = "comparable" /> is <see langword="null"/> .
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T, TComparable>(
+ this Span<T> span, TComparable comparable)
+ where TComparable : IComparable<T>
+ {
+ return BinarySearch((ReadOnlySpan<T>)span, comparable);
+ }
+
+ /// <summary>
+ /// Searches an entire sorted <see cref="Span{T}"/> for the specified <paramref name="value"/>
+ /// using the specified <typeparamref name="TComparer"/> generic type.
+ /// </summary>
+ /// <typeparam name="T">The element type of the span.</typeparam>
+ /// <typeparam name="TComparer">The specific type of <see cref="IComparer{T}"/>.</typeparam>
+ /// <param name="span">The sorted <see cref="Span{T}"/> to search.</param>
+ /// <param name="value">The object to locate. The value can be null for reference types.</param>
+ /// <param name="comparer">The <typeparamref name="TComparer"/> to use when comparing.</param>
+ /// /// <returns>
+ /// The zero-based index of <paramref name="value"/> in the sorted <paramref name="span"/>,
+ /// if <paramref name="value"/> is found; otherwise, a negative number that is the bitwise complement
+ /// of the index of the next element that is larger than <paramref name="value"/> or, if there is
+ /// no larger element, the bitwise complement of <see cref="Span{T}.Length"/>.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// <paramref name = "comparer" /> is <see langword="null"/> .
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T, TComparer>(
+ this Span<T> span, T value, TComparer comparer)
+ where TComparer : IComparer<T>
+ {
+ return BinarySearch((ReadOnlySpan<T>)span, value, comparer);
+ }
+
+ /// <summary>
+ /// Searches an entire sorted <see cref="ReadOnlySpan{T}"/> for a value
+ /// using the specified <see cref="IComparable{T}"/> generic interface.
+ /// </summary>
+ /// <typeparam name="T">The element type of the span.</typeparam>
+ /// <param name="span">The sorted <see cref="ReadOnlySpan{T}"/> to search.</param>
+ /// <param name="comparable">The <see cref="IComparable{T}"/> to use when comparing.</param>
+ /// <returns>
+ /// The zero-based index of <paramref name="comparable"/> in the sorted <paramref name="span"/>,
+ /// if <paramref name="comparable"/> is found; otherwise, a negative number that is the bitwise complement
+ /// of the index of the next element that is larger than <paramref name="comparable"/> or, if there is
+ /// no larger element, the bitwise complement of <see cref="ReadOnlySpan{T}.Length"/>.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// <paramref name = "comparable" /> is <see langword="null"/> .
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T>(
+ this ReadOnlySpan<T> span, IComparable<T> comparable)
+ {
+ return BinarySearch<T, IComparable<T>>(span, comparable);
+ }
+
+ /// <summary>
+ /// Searches an entire sorted <see cref="ReadOnlySpan{T}"/> for a value
+ /// using the specified <typeparamref name="TComparable"/> generic type.
+ /// </summary>
+ /// <typeparam name="T">The element type of the span.</typeparam>
+ /// <typeparam name="TComparable">The specific type of <see cref="IComparable{T}"/>.</typeparam>
+ /// <param name="span">The sorted <see cref="ReadOnlySpan{T}"/> to search.</param>
+ /// <param name="comparable">The <typeparamref name="TComparable"/> to use when comparing.</param>
+ /// <returns>
+ /// The zero-based index of <paramref name="comparable"/> in the sorted <paramref name="span"/>,
+ /// if <paramref name="comparable"/> is found; otherwise, a negative number that is the bitwise complement
+ /// of the index of the next element that is larger than <paramref name="comparable"/> or, if there is
+ /// no larger element, the bitwise complement of <see cref="ReadOnlySpan{T}.Length"/>.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// <paramref name = "comparable" /> is <see langword="null"/> .
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T, TComparable>(
+ this ReadOnlySpan<T> span, TComparable comparable)
+ where TComparable : IComparable<T>
+ {
+ return SpanHelpers.BinarySearch(span, comparable);
+ }
+
+ /// <summary>
+ /// Searches an entire sorted <see cref="ReadOnlySpan{T}"/> for the specified <paramref name="value"/>
+ /// using the specified <typeparamref name="TComparer"/> generic type.
+ /// </summary>
+ /// <typeparam name="T">The element type of the span.</typeparam>
+ /// <typeparam name="TComparer">The specific type of <see cref="IComparer{T}"/>.</typeparam>
+ /// <param name="span">The sorted <see cref="ReadOnlySpan{T}"/> to search.</param>
+ /// <param name="value">The object to locate. The value can be null for reference types.</param>
+ /// <param name="comparer">The <typeparamref name="TComparer"/> to use when comparing.</param>
+ /// /// <returns>
+ /// The zero-based index of <paramref name="value"/> in the sorted <paramref name="span"/>,
+ /// if <paramref name="value"/> is found; otherwise, a negative number that is the bitwise complement
+ /// of the index of the next element that is larger than <paramref name="value"/> or, if there is
+ /// no larger element, the bitwise complement of <see cref="ReadOnlySpan{T}.Length"/>.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// <paramref name = "comparer" /> is <see langword="null"/> .
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T, TComparer>(
+ this ReadOnlySpan<T> span, T value, TComparer comparer)
+ where TComparer : IComparer<T>
+ {
+ if (comparer == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparer);
+
+ var comparable = new SpanHelpers.ComparerComparable<T, TComparer>(
+ value, comparer);
+ return BinarySearch(span, comparable);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Number.Formatting.cs b/src/System.Private.CoreLib/shared/System/Number.Formatting.cs
index 58d501b09..24d5db1da 100644
--- a/src/System.Private.CoreLib/shared/System/Number.Formatting.cs
+++ b/src/System.Private.CoreLib/shared/System/Number.Formatting.cs
@@ -564,7 +564,7 @@ namespace System
{
Debug.Assert(source != null);
- if (source.AsReadOnlySpan().TryCopyTo(destination))
+ if (source.AsSpan().TryCopyTo(destination))
{
charsWritten = source.Length;
return true;
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs
index 7fabf1e2f..6544081df 100644
--- a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs
@@ -35,6 +35,16 @@ namespace System.Runtime.InteropServices
return true;
}
+ if (length == 0)
+ {
+#if FEATURE_PORTABLE_SPAN
+ arraySegment = new ArraySegment<T>(SpanHelpers.PerTypeValues<T>.EmptyArray);
+#else
+ arraySegment = ArraySegment<T>.Empty;
+#endif // FEATURE_PORTABLE_SPAN
+ return true;
+ }
+
arraySegment = default;
return false;
}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/StringBuffer.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/StringBuffer.cs
deleted file mode 100644
index fdd0b9559..000000000
--- a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/StringBuffer.cs
+++ /dev/null
@@ -1,301 +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.Buffers;
-using System.Runtime.CompilerServices;
-
-namespace System.Runtime.InteropServices
-{
- /// <summary>
- /// Buffer that deals in char size increments. Dispose to free memory. Always makes ordinal
- /// comparisons. Not thread safe.
- ///
- /// A more performant replacement for StringBuilder when performing native interop.
- ///
- /// "No copy" valuetype. Has to be passed as "ref".
- ///
- /// </summary>
- /// <remarks>
- /// Suggested use through P/Invoke: define DllImport arguments that take a character buffer as SafeHandle and pass StringBuffer.GetHandle().
- /// </remarks>
- internal struct StringBuffer
- {
- private char[] _buffer;
- private int _length;
-
- /// <summary>
- /// Instantiate the buffer with capacity for at least the specified number of characters. Capacity
- /// includes the trailing null character.
- /// </summary>
- public StringBuffer(int initialCapacity)
- {
- _buffer = ArrayPool<char>.Shared.Rent(initialCapacity);
- _length = 0;
- }
-
- /// <summary>
- /// Get/set the character at the given index.
- /// </summary>
- /// <exception cref="ArgumentOutOfRangeException">Thrown if attempting to index outside of the buffer length.</exception>
- public char this[int index]
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
- {
- if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index));
- return _buffer[index];
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- set
- {
- if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index));
- _buffer[index] = value;
- }
- }
-
- /// <summary>
- /// Underlying storage of the buffer. Used for interop.
- /// </summary>
- public char[] UnderlyingArray => _buffer;
-
- /// <summary>
- /// Character capacity of the buffer. Includes the count for the trailing null character.
- /// </summary>
- public int Capacity => _buffer.Length;
-
- /// <summary>
- /// Ensure capacity in characters is at least the given minimum.
- /// </summary>
- /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception>
- public void EnsureCapacity(int minCapacity)
- {
- if (minCapacity > Capacity)
- {
- char[] oldBuffer = _buffer;
- _buffer = ArrayPool<char>.Shared.Rent(minCapacity);
- Array.Copy(oldBuffer, 0, _buffer, 0, oldBuffer.Length);
- ArrayPool<char>.Shared.Return(oldBuffer);
- }
- }
-
- /// <summary>
- /// The logical length of the buffer in characters. (Does not include the final null.) Will automatically attempt to increase capacity.
- /// This is where the usable data ends.
- /// </summary>
- /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception>
- /// <exception cref="ArgumentOutOfRangeException">Thrown if the set size in bytes is int.MaxValue (as space is implicitly reserved for the trailing null).</exception>
- public int Length
- {
- get { return _length; }
- set
- {
- // Null terminate
- EnsureCapacity(checked(value + 1));
- _buffer[value] = '\0';
-
- _length = value;
- }
- }
-
- /// <summary>
- /// True if the buffer contains the given character.
- /// </summary>
- public unsafe bool Contains(char value)
- {
- fixed (char* start = _buffer)
- {
- int length = _length;
- for (int i = 0; i < length; i++)
- {
- if (start[i] == value) return true;
- }
- }
-
- return false;
- }
-
- /// <summary>
- /// Returns true if the buffer starts with the given string.
- /// </summary>
- public bool StartsWith(string value)
- {
- if (value == null) throw new ArgumentNullException(nameof(value));
- if (_length < value.Length) return false;
- return SubstringEquals(value, startIndex: 0, count: value.Length);
- }
-
- /// <summary>
- /// Returns true if the specified StringBuffer substring equals the given value.
- /// </summary>
- /// <param name="value">The value to compare against the specified substring.</param>
- /// <param name="startIndex">Start index of the sub string.</param>
- /// <param name="count">Length of the substring, or -1 to check all remaining.</param>
- /// <exception cref="ArgumentOutOfRangeException">
- /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
- /// of the buffer's length.
- /// </exception>
- public unsafe bool SubstringEquals(string value, int startIndex = 0, int count = -1)
- {
- if (value == null) return false;
- if (count < -1) throw new ArgumentOutOfRangeException(nameof(count));
- if (startIndex > _length) throw new ArgumentOutOfRangeException(nameof(startIndex));
-
- int realCount = count == -1 ? _length - startIndex : (int)count;
- if (checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count));
-
- int length = value.Length;
-
- // Check the substring length against the input length
- if (realCount != length) return false;
-
- fixed (char* valueStart = value)
- fixed (char* bufferStart = _buffer)
- {
- char* subStringStart = bufferStart + startIndex;
-
- for (int i = 0; i < length; i++)
- {
- if (subStringStart[i] != valueStart[i]) return false;
- }
- }
-
- return true;
- }
-
- /// <summary>
- /// Append the given buffer.
- /// </summary>
- /// <param name="value">The buffer to append.</param>
- /// <param name="startIndex">The index in the input buffer to start appending from.</param>
- /// <param name="count">The count of characters to copy from the buffer string.</param>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception>
- /// <exception cref="ArgumentOutOfRangeException">
- /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
- /// of <paramref name="value"/> characters.
- /// </exception>
- public void Append(ref StringBuffer value, int startIndex = 0)
- {
- if (value.Length == 0) return;
-
- value.CopyTo(
- bufferIndex: startIndex,
- destination: ref this,
- destinationIndex: _length,
- count: value.Length);
- }
-
- /// <summary>
- /// Append the given buffer.
- /// </summary>
- /// <param name="value">The buffer to append.</param>
- /// <param name="startIndex">The index in the input buffer to start appending from.</param>
- /// <param name="count">The count of characters to copy from the buffer string.</param>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception>
- /// <exception cref="ArgumentOutOfRangeException">
- /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
- /// of <paramref name="value"/> characters.
- /// </exception>
- public void Append(ref StringBuffer value, int startIndex, int count)
- {
- if (count == 0) return;
-
- value.CopyTo(
- bufferIndex: startIndex,
- destination: ref this,
- destinationIndex: _length,
- count: count);
- }
-
- /// <summary>
- /// Copy contents to the specified buffer. Destination index must be within current destination length.
- /// Will grow the destination buffer if needed.
- /// </summary>
- /// <exception cref="ArgumentOutOfRangeException">
- /// Thrown if <paramref name="bufferIndex"/> or <paramref name="destinationIndex"/> or <paramref name="count"/> are outside the range
- /// of <paramref name="value"/> characters.
- /// </exception>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="destination"/> is null.</exception>
- public void CopyTo(int bufferIndex, ref StringBuffer destination, int destinationIndex, int count)
- {
- if (destinationIndex > destination._length) throw new ArgumentOutOfRangeException(nameof(destinationIndex));
- if (bufferIndex >= _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex));
- if (_length < checked(bufferIndex + count)) throw new ArgumentOutOfRangeException(nameof(count));
-
- if (count == 0) return;
- int lastIndex = checked(destinationIndex + count);
- if (destination.Length < lastIndex) destination.Length = lastIndex;
-
- Array.Copy(UnderlyingArray, bufferIndex, destination.UnderlyingArray, destinationIndex, count);
- }
-
- /// <summary>
- /// Copy contents from the specified string into the buffer at the given index. Start index must be within the current length of
- /// the buffer, will grow as necessary.
- /// </summary>
- public void CopyFrom(int bufferIndex, string source, int sourceIndex = 0, int count = -1)
- {
- if (source == null) throw new ArgumentNullException(nameof(source));
- if (bufferIndex > _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex));
- if (sourceIndex < 0 || sourceIndex > source.Length) throw new ArgumentOutOfRangeException(nameof(sourceIndex));
- if (count == -1) count = source.Length - sourceIndex;
- if (count < 0 || source.Length - count < sourceIndex) throw new ArgumentOutOfRangeException(nameof(count));
-
- if (count == 0) return;
- int lastIndex = bufferIndex + (int)count;
- if (_length < lastIndex) Length = lastIndex;
-
- source.CopyTo(sourceIndex, UnderlyingArray, bufferIndex, count);
- }
-
- /// <summary>
- /// Trim the specified values from the end of the buffer. If nothing is specified, nothing is trimmed.
- /// </summary>
- public void TrimEnd(char[] values)
- {
- if (values == null || values.Length == 0 || _length == 0) return;
-
- while (_length > 0 && Array.IndexOf(values, _buffer[_length - 1]) >= 0)
- {
- Length = _length - 1;
- }
- }
-
- /// <summary>
- /// String representation of the entire buffer. If the buffer is larger than the maximum size string (int.MaxValue) this will throw.
- /// </summary>
- /// <exception cref="InvalidOperationException">Thrown if the buffer is too big to fit into a string.</exception>
- public override string ToString()
- {
- return new string(_buffer, startIndex: 0, length: _length);
- }
-
- /// <summary>
- /// Get the given substring in the buffer.
- /// </summary>
- /// <param name="count">Count of characters to take, or remaining characters from <paramref name="startIndex"/> if -1.</param>
- /// <exception cref="ArgumentOutOfRangeException">
- /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range of the buffer's length
- /// or count is greater than the maximum string size (int.MaxValue).
- /// </exception>
- public string Substring(int startIndex, int count = -1)
- {
- if (startIndex > (_length == 0 ? 0 : _length - 1)) throw new ArgumentOutOfRangeException(nameof(startIndex));
- if (count < -1) throw new ArgumentOutOfRangeException(nameof(count));
-
- int realCount = count == -1 ? _length - startIndex : (int)count;
- if (realCount > int.MaxValue || checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count));
-
- // The buffer could be bigger than will fit into a string, but the substring might fit. As the starting
- // index might be bigger than int we need to index ourselves.
- return new string(_buffer, startIndex: startIndex, length: realCount);
- }
-
- public void Free()
- {
- ArrayPool<char>.Shared.Return(_buffer);
- _buffer = null;
- _length = 0;
- }
- }
-}
diff --git a/src/System.Private.CoreLib/shared/System/Single.cs b/src/System.Private.CoreLib/shared/System/Single.cs
index df97427d3..7bffa1ac7 100644
--- a/src/System.Private.CoreLib/shared/System/Single.cs
+++ b/src/System.Private.CoreLib/shared/System/Single.cs
@@ -16,6 +16,8 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
+using Internal.Runtime.CompilerServices;
+
namespace System
{
[Serializable]
@@ -213,16 +215,18 @@ namespace System
return IsNaN(obj) && IsNaN(m_value);
}
- public unsafe override int GetHashCode()
+ public override int GetHashCode()
{
- float f = m_value;
- if (f == 0)
+ var bits = Unsafe.As<float, int>(ref m_value);
+
+ // Optimized check for IsNan() || IsZero()
+ if (((bits - 1) & 0x7FFFFFFF) >= 0x7F800000)
{
- // Ensure that 0 and -0 have the same hash code
- return 0;
+ // Ensure that all NaNs and both zeros have the same hash code
+ bits &= 0x7F800000;
}
- int v = *(int*)(&f);
- return v;
+
+ return bits;
}
public override String ToString()
diff --git a/src/System.Private.CoreLib/shared/System/Span.Fast.cs b/src/System.Private.CoreLib/shared/System/Span.Fast.cs
index 36493cd88..0ae1922fd 100644
--- a/src/System.Private.CoreLib/shared/System/Span.Fast.cs
+++ b/src/System.Private.CoreLib/shared/System/Span.Fast.cs
@@ -162,11 +162,11 @@ namespace System
{
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
- Span.ClearWithReferences(ref Unsafe.As<T, IntPtr>(ref _pointer.Value), (nuint)_length * (nuint)(Unsafe.SizeOf<T>() / sizeof(nuint)));
+ SpanHelpers.ClearWithReferences(ref Unsafe.As<T, IntPtr>(ref _pointer.Value), (nuint)_length * (nuint)(Unsafe.SizeOf<T>() / sizeof(nuint)));
}
else
{
- Span.ClearWithoutReferences(ref Unsafe.As<T, byte>(ref _pointer.Value), (nuint)_length * (nuint)Unsafe.SizeOf<T>());
+ SpanHelpers.ClearWithoutReferences(ref Unsafe.As<T, byte>(ref _pointer.Value), (nuint)_length * (nuint)Unsafe.SizeOf<T>());
}
}
diff --git a/src/System.Private.CoreLib/shared/System/Span.NonGeneric.cs b/src/System.Private.CoreLib/shared/System/Span.NonGeneric.cs
index 4ef1c233e..4f2892d4c 100644
--- a/src/System.Private.CoreLib/shared/System/Span.NonGeneric.cs
+++ b/src/System.Private.CoreLib/shared/System/Span.NonGeneric.cs
@@ -23,862 +23,19 @@ namespace System
/// </summary>
public static class Span
{
- /// <summary>
- /// Copies the characters from the source span into the destination, converting each character to lowercase.
- /// </summary>
- public static int ToLower(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
- {
- if (culture == null)
- ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture);
+ public static Span<byte> AsBytes<T>(Span<T> source)
+ where T : struct => source.AsBytes();
- // Assuming that changing case does not affect length
- if (destination.Length < source.Length)
- return -1;
+ public static ReadOnlySpan<byte> AsBytes<T>(ReadOnlySpan<T> source)
+ where T : struct => source.AsBytes();
- if (GlobalizationMode.Invariant)
- culture.TextInfo.ToLowerAsciiInvariant(source, destination);
- else
- culture.TextInfo.ChangeCase(source, destination, toUpper: false);
- return source.Length;
- }
+ // TODO: Delete once the AsReadOnlySpan -> AsSpan rename propages through the system
+ public static ReadOnlySpan<char> AsReadOnlySpan(this string text) => text.AsSpan();
+ public static ReadOnlySpan<char> AsReadOnlySpan(this string text, int start) => text.AsSpan(start);
+ public static ReadOnlySpan<char> AsReadOnlySpan(this string text, int start, int length) => text.AsSpan(start, length);
- /// <summary>
- /// Copies the characters from the source span into the destination, converting each character to lowercase
- /// using the casing rules of the invariant culture.
- /// </summary>
- public static int ToLowerInvariant(this ReadOnlySpan<char> source, Span<char> destination)
- {
- // Assuming that changing case does not affect length
- if (destination.Length < source.Length)
- return -1;
-
- if (GlobalizationMode.Invariant)
- CultureInfo.InvariantCulture.TextInfo.ToLowerAsciiInvariant(source, destination);
- else
- CultureInfo.InvariantCulture.TextInfo.ChangeCase(source, destination, toUpper: false);
- return source.Length;
- }
-
- /// <summary>
- /// Copies the characters from the source span into the destination, converting each character to uppercase.
- /// </summary>
- public static int ToUpper(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
- {
- if (culture == null)
- ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture);
-
- // Assuming that changing case does not affect length
- if (destination.Length < source.Length)
- return -1;
-
- if (GlobalizationMode.Invariant)
- culture.TextInfo.ToUpperAsciiInvariant(source, destination);
- else
- culture.TextInfo.ChangeCase(source, destination, toUpper: true);
- return source.Length;
- }
-
- /// <summary>
- /// Copies the characters from the source span into the destination, converting each character to uppercase
- /// using the casing rules of the invariant culture.
- /// </summary>
- public static int ToUpperInvariant(this ReadOnlySpan<char> source, Span<char> destination)
- {
- // Assuming that changing case does not affect length
- if (destination.Length < source.Length)
- return -1;
-
- if (GlobalizationMode.Invariant)
- CultureInfo.InvariantCulture.TextInfo.ToUpperAsciiInvariant(source, destination);
- else
- CultureInfo.InvariantCulture.TextInfo.ChangeCase(source, destination, toUpper: true);
- return source.Length;
- }
-
- /// <summary>
- /// Determines whether the beginning of the span matches the specified value when compared using the specified comparison option.
- /// </summary>
- public static bool StartsWith(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
- {
- if (value.Length == 0)
- {
- StringSpanHelpers.CheckStringComparison(comparisonType);
- return true;
- }
-
- switch (comparisonType)
- {
- case StringComparison.CurrentCulture:
- return StartsWithCultureHelper(span, value);
-
- case StringComparison.CurrentCultureIgnoreCase:
- return StartsWithCultureIgnoreCaseHelper(span, value);
-
- case StringComparison.InvariantCulture:
- return StartsWithCultureHelper(span, value, invariantCulture: true);
-
- case StringComparison.InvariantCultureIgnoreCase:
- return StartsWithCultureIgnoreCaseHelper(span, value, invariantCulture: true);
-
- case StringComparison.Ordinal:
- return StartsWithOrdinalHelper(span, value);
-
- case StringComparison.OrdinalIgnoreCase:
- return StartsWithOrdinalIgnoreCaseHelper(span, value);
-
- default:
- throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
- }
- }
-
- internal static bool StartsWithCultureHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, bool invariantCulture = false)
- {
- Debug.Assert(value.Length != 0);
-
- if (GlobalizationMode.Invariant)
- {
- return StartsWithOrdinalHelper(span, value);
- }
- if (span.Length == 0)
- {
- return false;
- }
- return invariantCulture ?
- CompareInfo.Invariant.IsPrefix(span, value, CompareOptions.None) :
- CultureInfo.CurrentCulture.CompareInfo.IsPrefix(span, value, CompareOptions.None);
- }
-
- internal static bool StartsWithCultureIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, bool invariantCulture = false)
- {
- Debug.Assert(value.Length != 0);
-
- if (GlobalizationMode.Invariant)
- {
- return StartsWithOrdinalIgnoreCaseHelper(span, value);
- }
- if (span.Length == 0)
- {
- return false;
- }
- return invariantCulture ?
- CompareInfo.Invariant.IsPrefix(span, value, CompareOptions.IgnoreCase) :
- CultureInfo.CurrentCulture.CompareInfo.IsPrefix(span, value, CompareOptions.IgnoreCase);
- }
-
- internal static bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
- {
- Debug.Assert(value.Length != 0);
-
- if (span.Length < value.Length)
- {
- return false;
- }
- return CompareInfo.CompareOrdinalIgnoreCase(span.Slice(0, value.Length), value) == 0;
- }
-
- internal static unsafe bool StartsWithOrdinalHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
- {
- Debug.Assert(value.Length != 0);
-
- if (span.Length < value.Length)
- {
- return false;
- }
- return OrdinalHelper(span, value, value.Length);
- }
-
- internal static unsafe bool OrdinalHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, int length)
- {
- Debug.Assert(length != 0);
- Debug.Assert(span.Length >= length);
-
- fixed (char* ap = &MemoryMarshal.GetReference(span))
- fixed (char* bp = &MemoryMarshal.GetReference(value))
- {
- char* a = ap;
- char* b = bp;
-
-#if BIT64
- // Single int read aligns pointers for the following long reads
- if (length >= 2)
- {
- if (*(int*)a != *(int*)b)
- return false;
- length -= 2;
- a += 2;
- b += 2;
- }
-
- while (length >= 12)
- {
- if (*(long*)a != *(long*)b)
- return false;
- if (*(long*)(a + 4) != *(long*)(b + 4))
- return false;
- if (*(long*)(a + 8) != *(long*)(b + 8))
- return false;
- length -= 12;
- a += 12;
- b += 12;
- }
-#else
- while (length >= 10)
- {
- if (*(int*)a != *(int*)b) return false;
- if (*(int*)(a+2) != *(int*)(b+2)) return false;
- if (*(int*)(a+4) != *(int*)(b+4)) return false;
- if (*(int*)(a+6) != *(int*)(b+6)) return false;
- if (*(int*)(a+8) != *(int*)(b+8)) return false;
- length -= 10; a += 10; b += 10;
- }
-#endif
-
- while (length >= 2)
- {
- if (*(int*)a != *(int*)b)
- return false;
- length -= 2;
- a += 2;
- b += 2;
- }
-
- // PERF: This depends on the fact that the String objects are always zero terminated
- // and that the terminating zero is not included in the length. For even string sizes
- // this compare can include the zero terminator. Bitwise OR avoids a branch.
- return length == 0 | *a == *b;
- }
- }
-
- /// <summary>
- /// Determines whether the end of the span matches the specified value when compared using the specified comparison option.
- /// </summary>
- public static bool EndsWith(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
- {
- if (value.Length == 0)
- {
- StringSpanHelpers.CheckStringComparison(comparisonType);
- return true;
- }
-
- switch (comparisonType)
- {
- case StringComparison.CurrentCulture:
- return EndsWithCultureHelper(span, value);
-
- case StringComparison.CurrentCultureIgnoreCase:
- return EndsWithCultureIgnoreCaseHelper(span, value);
-
- case StringComparison.InvariantCulture:
- return EndsWithCultureHelper(span, value, invariantCulture: true);
-
- case StringComparison.InvariantCultureIgnoreCase:
- return EndsWithCultureIgnoreCaseHelper(span, value, invariantCulture: true);
-
- case StringComparison.Ordinal:
- return EndsWithOrdinalHelper(span, value);
-
- case StringComparison.OrdinalIgnoreCase:
- return EndsWithOrdinalIgnoreCaseHelper(span, value);
-
- default:
- throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
- }
- }
-
- internal static bool EndsWithCultureHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, bool invariantCulture = false)
- {
- Debug.Assert(value.Length != 0);
-
- if (GlobalizationMode.Invariant)
- {
- return EndsWithOrdinalHelper(span, value);
- }
- if (span.Length == 0)
- {
- return false;
- }
- return invariantCulture ?
- CompareInfo.Invariant.IsSuffix(span, value, CompareOptions.None) :
- CultureInfo.CurrentCulture.CompareInfo.IsSuffix(span, value, CompareOptions.None);
- }
-
- internal static bool EndsWithCultureIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, bool invariantCulture = false)
- {
- Debug.Assert(value.Length != 0);
-
- if (GlobalizationMode.Invariant)
- {
- return EndsWithOrdinalIgnoreCaseHelper(span, value);
- }
- if (span.Length == 0)
- {
- return false;
- }
- return invariantCulture ?
- CompareInfo.Invariant.IsSuffix(span, value, CompareOptions.IgnoreCase) :
- CultureInfo.CurrentCulture.CompareInfo.IsSuffix(span, value, CompareOptions.IgnoreCase);
- }
-
- internal static bool EndsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
- {
- Debug.Assert(value.Length != 0);
-
- if (span.Length < value.Length)
- {
- return false;
- }
- return (CompareInfo.CompareOrdinalIgnoreCase(span.Slice(span.Length - value.Length), value) == 0);
- }
-
- internal static unsafe bool EndsWithOrdinalHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
- {
- Debug.Assert(value.Length != 0);
-
- if (span.Length < value.Length)
- {
- return false;
- }
- return OrdinalHelper(span.Slice(span.Length - value.Length), value, value.Length);
- }
-
- /// <summary>Creates a new <see cref="ReadOnlyMemory{char}"/> over the portion of the target string.</summary>
- /// <param name="text">The target string.</param>
- /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
- public static ReadOnlyMemory<char> AsReadOnlyMemory(this string text)
- {
- if (text == null)
- return default;
-
- return new ReadOnlyMemory<char>(text, 0, text.Length);
- }
-
- /// <summary>Creates a new <see cref="ReadOnlyMemory{char}"/> over the portion of the target string.</summary>
- /// <param name="text">The target string.</param>
- /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
- /// <exception cref="System.ArgumentOutOfRangeException">
- /// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;text.Length).
- /// </exception>
- public static ReadOnlyMemory<char> AsReadOnlyMemory(this string text, int start)
- {
- if (text == null)
- {
- if (start != 0)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- return default;
- }
-
- if ((uint)start > (uint)text.Length)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
-
- return new ReadOnlyMemory<char>(text, start, text.Length - start);
- }
-
- /// <summary>Creates a new <see cref="ReadOnlyMemory{char}"/> over the portion of the target string.</summary>
- /// <param name="text">The target string.</param>
- /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
- /// <exception cref="System.ArgumentOutOfRangeException">
- /// Thrown when the specified <paramref name="start"/> index or <paramref name="length"/> is not in range.
- /// </exception>
- public static ReadOnlyMemory<char> AsReadOnlyMemory(this string text, int start, int length)
- {
- if (text == null)
- {
- if (start != 0 || length != 0)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- return default;
- }
-
- if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start))
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
-
- return new ReadOnlyMemory<char>(text, start, length);
- }
-
- /// <summary>Attempts to get the underlying <see cref="string"/> from a <see cref="ReadOnlyMemory{T}"/>.</summary>
- /// <param name="readOnlyMemory">The memory that may be wrapping a <see cref="string"/> object.</param>
- /// <param name="text">The string.</param>
- /// <param name="start">The starting location in <paramref name="text"/>.</param>
- /// <param name="length">The number of items in <paramref name="text"/>.</param>
- /// <returns></returns>
- public static bool TryGetString(this ReadOnlyMemory<char> readOnlyMemory, out string text, out int start, out int length)
- {
- if (readOnlyMemory.GetObjectStartLength(out int offset, out int count) is string s)
- {
- text = s;
- start = offset;
- length = count;
- return true;
- }
- else
- {
- text = null;
- start = 0;
- length = 0;
- return false;
- }
- }
-
- /// <summary>
- /// Casts a Span of one primitive type <typeparamref name="T"/> to Span of bytes.
- /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
- /// </summary>
- /// <param name="source">The source slice, of type <typeparamref name="T"/>.</param>
- /// <exception cref="System.ArgumentException">
- /// Thrown when <typeparamref name="T"/> contains pointers.
- /// </exception>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Span<byte> AsBytes<T>(this Span<T> source)
- where T : struct
- {
- if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
- ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
-
- return new Span<byte>(
- ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(source)),
- checked(source.Length * Unsafe.SizeOf<T>()));
- }
-
- /// <summary>
- /// Casts a ReadOnlySpan of one primitive type <typeparamref name="T"/> to ReadOnlySpan of bytes.
- /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
- /// </summary>
- /// <param name="source">The source slice, of type <typeparamref name="T"/>.</param>
- /// <exception cref="System.ArgumentException">
- /// Thrown when <typeparamref name="T"/> contains pointers.
- /// </exception>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ReadOnlySpan<byte> AsBytes<T>(this ReadOnlySpan<T> source)
- where T : struct
- {
- if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
- ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
-
- return new ReadOnlySpan<byte>(
- ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(source)),
- checked(source.Length * Unsafe.SizeOf<T>()));
- }
-
- /// <summary>
- /// Creates a new readonly span over the portion of the target string.
- /// </summary>
- /// <param name="text">The target string.</param>
- /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
- /// reference (Nothing in Visual Basic).</exception>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ReadOnlySpan<char> AsReadOnlySpan(this string text)
- {
- if (text == null)
- return default;
-
- return new ReadOnlySpan<char>(ref text.GetRawStringData(), text.Length);
- }
-
- /// <summary>
- /// Creates a new readonly span over the portion of the target string.
- /// </summary>
- /// <param name="text">The target string.</param>
- /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
- /// <exception cref="System.ArgumentOutOfRangeException">
- /// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;text.Length).
- /// </exception>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ReadOnlySpan<char> AsReadOnlySpan(this string text, int start)
- {
- if (text == null)
- {
- if (start != 0)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- return default;
- }
-
- if ((uint)start > (uint)text.Length)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
-
- return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), start), text.Length - start);
- }
-
- /// <summary>
- /// Creates a new readonly span over the portion of the target string.
- /// </summary>
- /// <param name="text">The target string.</param>
- /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
- /// <exception cref="System.ArgumentOutOfRangeException">
- /// Thrown when the specified <paramref name="start"/> index or <paramref name="length"/> is not in range.
- /// </exception>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ReadOnlySpan<char> AsReadOnlySpan(this string text, int start, int length)
- {
- if (text == null)
- {
- if (start != 0 || length != 0)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- return default;
- }
-
- if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start))
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
-
- return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), start), length);
- }
-
- internal static unsafe void ClearWithoutReferences(ref byte b, nuint byteLength)
- {
- if (byteLength == 0)
- return;
-
-#if CORECLR && (AMD64 || ARM64)
- if (byteLength > 4096)
- goto PInvoke;
- Unsafe.InitBlockUnaligned(ref b, 0, (uint)byteLength);
- return;
-#else
- // TODO: Optimize other platforms to be on par with AMD64 CoreCLR
- // Note: It's important that this switch handles lengths at least up to 22.
- // See notes below near the main loop for why.
-
- // The switch will be very fast since it can be implemented using a jump
- // table in assembly. See http://stackoverflow.com/a/449297/4077294 for more info.
-
- switch (byteLength)
- {
- case 1:
- b = 0;
- return;
- case 2:
- Unsafe.As<byte, short>(ref b) = 0;
- return;
- case 3:
- Unsafe.As<byte, short>(ref b) = 0;
- Unsafe.Add<byte>(ref b, 2) = 0;
- return;
- case 4:
- Unsafe.As<byte, int>(ref b) = 0;
- return;
- case 5:
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.Add<byte>(ref b, 4) = 0;
- return;
- case 6:
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
- return;
- case 7:
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
- Unsafe.Add<byte>(ref b, 6) = 0;
- return;
- case 8:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
-#endif
- return;
- case 9:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
-#endif
- Unsafe.Add<byte>(ref b, 8) = 0;
- return;
- case 10:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
-#endif
- Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
- return;
- case 11:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
-#endif
- Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
- Unsafe.Add<byte>(ref b, 10) = 0;
- return;
- case 12:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
-#endif
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
- return;
- case 13:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
-#endif
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
- Unsafe.Add<byte>(ref b, 12) = 0;
- return;
- case 14:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
-#endif
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
- Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
- return;
- case 15:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
-#endif
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
- Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
- Unsafe.Add<byte>(ref b, 14) = 0;
- return;
- case 16:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
- Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
-#endif
- return;
- case 17:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
- Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
-#endif
- Unsafe.Add<byte>(ref b, 16) = 0;
- return;
- case 18:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
- Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
-#endif
- Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
- return;
- case 19:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
- Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
-#endif
- Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
- Unsafe.Add<byte>(ref b, 18) = 0;
- return;
- case 20:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
- Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
-#endif
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
- return;
- case 21:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
- Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
-#endif
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
- Unsafe.Add<byte>(ref b, 20) = 0;
- return;
- case 22:
-#if BIT64
- Unsafe.As<byte, long>(ref b) = 0;
- Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
-#else
- Unsafe.As<byte, int>(ref b) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
-#endif
- Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
- Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 20)) = 0;
- return;
- }
-
- // P/Invoke into the native version for large lengths
- if (byteLength >= 512) goto PInvoke;
-
- nuint i = 0; // byte offset at which we're copying
-
- if ((Unsafe.As<byte, int>(ref b) & 3) != 0)
- {
- if ((Unsafe.As<byte, int>(ref b) & 1) != 0)
- {
- Unsafe.AddByteOffset<byte>(ref b, i) = 0;
- i += 1;
- if ((Unsafe.As<byte, int>(ref b) & 2) != 0)
- goto IntAligned;
- }
- Unsafe.As<byte, short>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
- i += 2;
- }
-
- IntAligned:
-
- // On 64-bit IntPtr.Size == 8, so we want to advance to the next 8-aligned address. If
- // (int)b % 8 is 0, 5, 6, or 7, we will already have advanced by 0, 3, 2, or 1
- // bytes to the next aligned address (respectively), so do nothing. On the other hand,
- // if it is 1, 2, 3, or 4 we will want to copy-and-advance another 4 bytes until
- // we're aligned.
- // The thing 1, 2, 3, and 4 have in common that the others don't is that if you
- // subtract one from them, their 3rd lsb will not be set. Hence, the below check.
-
- if (((Unsafe.As<byte, int>(ref b) - 1) & 4) == 0)
- {
- Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
- i += 4;
- }
-
- nuint end = byteLength - 16;
- byteLength -= i; // lower 4 bits of byteLength represent how many bytes are left *after* the unrolled loop
-
- // We know due to the above switch-case that this loop will always run 1 iteration; max
- // bytes we clear before checking is 23 (7 to align the pointers, 16 for 1 iteration) so
- // the switch handles lengths 0-22.
- Debug.Assert(end >= 7 && i <= end);
-
- // This is separated out into a different variable, so the i + 16 addition can be
- // performed at the start of the pipeline and the loop condition does not have
- // a dependency on the writes.
- nuint counter;
-
- do
- {
- counter = i + 16;
-
- // This loop looks very costly since there appear to be a bunch of temporary values
- // being created with the adds, but the jit (for x86 anyways) will convert each of
- // these to use memory addressing operands.
-
- // So the only cost is a bit of code size, which is made up for by the fact that
- // we save on writes to b.
-
-#if BIT64
- Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
- Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i + 8)) = 0;
-#else
- Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 4)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 8)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 12)) = 0;
-#endif
-
- i = counter;
-
- // See notes above for why this wasn't used instead
- // i += 16;
- }
- while (counter <= end);
-
- if ((byteLength & 8) != 0)
- {
-#if BIT64
- Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
-#else
- Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
- Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 4)) = 0;
-#endif
- i += 8;
- }
- if ((byteLength & 4) != 0)
- {
- Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
- i += 4;
- }
- if ((byteLength & 2) != 0)
- {
- Unsafe.As<byte, short>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
- i += 2;
- }
- if ((byteLength & 1) != 0)
- {
- Unsafe.AddByteOffset<byte>(ref b, i) = 0;
- // We're not using i after this, so not needed
- // i += 1;
- }
-
- return;
-#endif
-
- PInvoke:
- RuntimeImports.RhZeroMemory(ref b, byteLength);
- }
-
- internal static unsafe void ClearWithReferences(ref IntPtr ip, nuint pointerSizeLength)
- {
- if (pointerSizeLength == 0)
- return;
-
- // TODO: Perhaps do switch casing to improve small size perf
-
- nuint i = 0;
- nuint n = 0;
- while ((n = i + 8) <= (pointerSizeLength))
- {
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 1) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 2) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 3) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 4) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 5) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 6) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 7) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- i = n;
- }
- if ((n = i + 4) <= (pointerSizeLength))
- {
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 1) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 2) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 3) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- i = n;
- }
- if ((n = i + 2) <= (pointerSizeLength))
- {
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 1) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- i = n;
- }
- if ((i + 1) <= (pointerSizeLength))
- {
- Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr);
- }
- }
+ public static ReadOnlyMemory<char> AsReadOnlyMemory(this string text) => text.AsMemory();
+ public static ReadOnlyMemory<char> AsReadOnlyMemory(this string text, int start) => text.AsMemory(start);
+ public static ReadOnlyMemory<char> AsReadOnlyMemory(this string text, int start, int length) => text.AsMemory(start, length);
}
}
diff --git a/src/System.Private.CoreLib/shared/System/SpanHelpers.BinarySearch.cs b/src/System.Private.CoreLib/shared/System/SpanHelpers.BinarySearch.cs
new file mode 100644
index 000000000..656b864e2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/SpanHelpers.BinarySearch.cs
@@ -0,0 +1,83 @@
+// 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.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+#if !netstandard
+using Internal.Runtime.CompilerServices;
+#endif
+
+namespace System
+{
+ internal static partial class SpanHelpers
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T, TComparable>(
+ this ReadOnlySpan<T> span, TComparable comparable)
+ where TComparable : IComparable<T>
+ {
+ if (comparable == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparable);
+
+ return BinarySearch(ref MemoryMarshal.GetReference(span), span.Length, comparable);
+ }
+
+ public static int BinarySearch<T, TComparable>(
+ ref T spanStart, int length, TComparable comparable)
+ where TComparable : IComparable<T>
+ {
+ int lo = 0;
+ int hi = length - 1;
+ // If length == 0, hi == -1, and loop will not be entered
+ while (lo <= hi)
+ {
+ // PERF: `lo` or `hi` will never be negative inside the loop,
+ // so computing median using uints is safe since we know
+ // `length <= int.MaxValue`, and indices are >= 0
+ // and thus cannot overflow an uint.
+ // Saves one subtraction per loop compared to
+ // `int i = lo + ((hi - lo) >> 1);`
+ int i = (int)(((uint)hi + (uint)lo) >> 1);
+
+ int c = comparable.CompareTo(Unsafe.Add(ref spanStart, i));
+ if (c == 0)
+ {
+ return i;
+ }
+ else if (c > 0)
+ {
+ lo = i + 1;
+ }
+ else
+ {
+ hi = i - 1;
+ }
+ }
+ // If none found, then a negative number that is the bitwise complement
+ // of the index of the next element that is larger than or, if there is
+ // no larger element, the bitwise complement of `length`, which
+ // is `lo` at this point.
+ return ~lo;
+ }
+
+ // Helper to allow sharing all code via IComparable<T> inlineable
+ internal struct ComparerComparable<T, TComparer> : IComparable<T>
+ where TComparer : IComparer<T>
+ {
+ readonly T _value;
+ readonly TComparer _comparer;
+
+ public ComparerComparable(T value, TComparer comparer)
+ {
+ _value = value;
+ _comparer = comparer;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int CompareTo(T other) => _comparer.Compare(_value, other);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/SpanHelpers.Byte.cs b/src/System.Private.CoreLib/shared/System/SpanHelpers.Byte.cs
new file mode 100644
index 000000000..860b2efa0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/SpanHelpers.Byte.cs
@@ -0,0 +1,1104 @@
+// 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;
+
+#if !netstandard
+using Internal.Runtime.CompilerServices;
+#endif
+
+#if !netstandard11
+using System.Numerics;
+#endif
+
+namespace System
+{
+ internal static partial class SpanHelpers
+ {
+ public static int IndexOf(ref byte searchSpace, int searchSpaceLength, ref byte value, int valueLength)
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ byte valueHead = value;
+ ref byte valueTail = ref Unsafe.Add(ref value, 1);
+ int valueTailLength = valueLength - 1;
+
+ int index = 0;
+ for (; ; )
+ {
+ Debug.Assert(0 <= index && index <= searchSpaceLength); // Ensures no deceptive underflows in the computation of "remainingSearchSpaceLength".
+ int remainingSearchSpaceLength = searchSpaceLength - index - valueTailLength;
+ if (remainingSearchSpaceLength <= 0)
+ break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there.
+
+ // Do a quick search for the first element of "value".
+ int relativeIndex = IndexOf(ref Unsafe.Add(ref searchSpace, index), valueHead, remainingSearchSpaceLength);
+ if (relativeIndex == -1)
+ break;
+ index += relativeIndex;
+
+ // Found the first element of "value". See if the tail matches.
+ if (SequenceEqual(ref Unsafe.Add(ref searchSpace, index + 1), ref valueTail, valueTailLength))
+ return index; // The tail matched. Return a successful find.
+
+ index++;
+ }
+ return -1;
+ }
+
+ public static int IndexOfAny(ref byte searchSpace, int searchSpaceLength, ref byte value, int valueLength)
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ int index = -1;
+ for (int i = 0; i < valueLength; i++)
+ {
+ var tempIndex = IndexOf(ref searchSpace, Unsafe.Add(ref value, i), searchSpaceLength);
+ if ((uint)tempIndex < (uint)index)
+ {
+ index = tempIndex;
+ // Reduce space for search, cause we don't care if we find the search value after the index of a previously found value
+ searchSpaceLength = tempIndex;
+
+ if (index == 0) break;
+ }
+ }
+ return index;
+ }
+
+ public static int LastIndexOfAny(ref byte searchSpace, int searchSpaceLength, ref byte value, int valueLength)
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ int index = -1;
+ for (int i = 0; i < valueLength; i++)
+ {
+ var tempIndex = LastIndexOf(ref searchSpace, Unsafe.Add(ref value, i), searchSpaceLength);
+ if (tempIndex > index) index = tempIndex;
+ }
+ return index;
+ }
+
+ public static unsafe int IndexOf(ref byte searchSpace, byte value, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ uint uValue = value; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ IntPtr index = (IntPtr)0; // Use UIntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr nLength = (IntPtr)(uint)length;
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
+ {
+ unchecked
+ {
+ int unaligned = (int)(byte*)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
+ nLength = (IntPtr)(uint)((Vector<byte>.Count - unaligned) & (Vector<byte>.Count - 1));
+ }
+ }
+ SequentialScan:
+#endif
+ while ((byte*)nLength >= (byte*)8)
+ {
+ nLength -= 8;
+
+ if (uValue == Unsafe.Add(ref searchSpace, index))
+ goto Found;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 1))
+ goto Found1;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 2))
+ goto Found2;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 3))
+ goto Found3;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 4))
+ goto Found4;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 5))
+ goto Found5;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 6))
+ goto Found6;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 7))
+ goto Found7;
+
+ index += 8;
+ }
+
+ if ((byte*)nLength >= (byte*)4)
+ {
+ nLength -= 4;
+
+ if (uValue == Unsafe.Add(ref searchSpace, index))
+ goto Found;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 1))
+ goto Found1;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 2))
+ goto Found2;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 3))
+ goto Found3;
+
+ index += 4;
+ }
+
+ while ((byte*)nLength > (byte*)0)
+ {
+ nLength -= 1;
+
+ if (uValue == Unsafe.Add(ref searchSpace, index))
+ goto Found;
+
+ index += 1;
+ }
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && ((int)(byte*)index < length))
+ {
+ nLength = (IntPtr)(uint)((length - (uint)index) & ~(Vector<byte>.Count - 1));
+ // Get comparison Vector
+ Vector<byte> vComparison = GetVector(value);
+ while ((byte*)nLength > (byte*)index)
+ {
+ var vMatches = Vector.Equals(vComparison, Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index)));
+ if (Vector<byte>.Zero.Equals(vMatches))
+ {
+ index += Vector<byte>.Count;
+ continue;
+ }
+ // Find offset of first match
+ return (int)(byte*)index + LocateFirstFoundByte(vMatches);
+ }
+
+ if ((int)(byte*)index < length)
+ {
+ unchecked
+ {
+ nLength = (IntPtr)(length - (int)(byte*)index);
+ }
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ public static int LastIndexOf(ref byte searchSpace, int searchSpaceLength, ref byte value, int valueLength)
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ byte valueHead = value;
+ ref byte valueTail = ref Unsafe.Add(ref value, 1);
+ int valueTailLength = valueLength - 1;
+
+ int index = 0;
+ for (; ; )
+ {
+ Debug.Assert(0 <= index && index <= searchSpaceLength); // Ensures no deceptive underflows in the computation of "remainingSearchSpaceLength".
+ int remainingSearchSpaceLength = searchSpaceLength - index - valueTailLength;
+ if (remainingSearchSpaceLength <= 0)
+ break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there.
+
+ // Do a quick search for the first element of "value".
+ int relativeIndex = LastIndexOf(ref searchSpace, valueHead, remainingSearchSpaceLength);
+ if (relativeIndex == -1)
+ break;
+
+ // Found the first element of "value". See if the tail matches.
+ if (SequenceEqual(ref Unsafe.Add(ref searchSpace, relativeIndex + 1), ref valueTail, valueTailLength))
+ return relativeIndex; // The tail matched. Return a successful find.
+
+ index += remainingSearchSpaceLength - relativeIndex;
+ }
+ return -1;
+ }
+
+ public static unsafe int LastIndexOf(ref byte searchSpace, byte value, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ uint uValue = value; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ IntPtr index = (IntPtr)(uint)length; // Use UIntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr nLength = (IntPtr)(uint)length;
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
+ {
+ unchecked
+ {
+ int unaligned = (int)(byte*)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
+ nLength = (IntPtr)(((length & (Vector<byte>.Count - 1)) + unaligned) & (Vector<byte>.Count - 1));
+ }
+ }
+ SequentialScan:
+#endif
+ while ((byte*)nLength >= (byte*)8)
+ {
+ nLength -= 8;
+ index -= 8;
+
+ if (uValue == Unsafe.Add(ref searchSpace, index + 7))
+ goto Found7;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 6))
+ goto Found6;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 5))
+ goto Found5;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 4))
+ goto Found4;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 3))
+ goto Found3;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 2))
+ goto Found2;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 1))
+ goto Found1;
+ if (uValue == Unsafe.Add(ref searchSpace, index))
+ goto Found;
+ }
+
+ if ((byte*)nLength >= (byte*)4)
+ {
+ nLength -= 4;
+ index -= 4;
+
+ if (uValue == Unsafe.Add(ref searchSpace, index + 3))
+ goto Found3;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 2))
+ goto Found2;
+ if (uValue == Unsafe.Add(ref searchSpace, index + 1))
+ goto Found1;
+ if (uValue == Unsafe.Add(ref searchSpace, index))
+ goto Found;
+ }
+
+ while ((byte*)nLength > (byte*)0)
+ {
+ nLength -= 1;
+ index -= 1;
+
+ if (uValue == Unsafe.Add(ref searchSpace, index))
+ goto Found;
+ }
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && ((int)(byte*)index > 0))
+ {
+ nLength = (IntPtr)(uint)((uint)index & ~(Vector<byte>.Count - 1));
+
+ // Get comparison Vector
+ Vector<byte> vComparison = GetVector(value);
+ while ((byte*)nLength > (byte*)(Vector<byte>.Count - 1))
+ {
+ var vMatches = Vector.Equals(vComparison, Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index - Vector<byte>.Count)));
+ if (Vector<byte>.Zero.Equals(vMatches))
+ {
+ index -= Vector<byte>.Count;
+ nLength -= Vector<byte>.Count;
+ continue;
+ }
+ // Find offset of first match
+ return (int)(byte*)(index) - Vector<byte>.Count + LocateLastFoundByte(vMatches);
+ }
+ if ((int)(byte*)index > 0)
+ {
+ nLength = index;
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ public static unsafe int IndexOfAny(ref byte searchSpace, byte value0, byte value1, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ IntPtr index = (IntPtr)0; // Use UIntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr nLength = (IntPtr)(uint)length;
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
+ {
+ unchecked
+ {
+ int unaligned = (int)(byte*)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
+ nLength = (IntPtr)(uint)((Vector<byte>.Count - unaligned) & (Vector<byte>.Count - 1));
+ }
+ }
+ SequentialScan:
+#endif
+ uint lookUp;
+ while ((byte*)nLength >= (byte*)8)
+ {
+ nLength -= 8;
+
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, index + 4);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found4;
+ lookUp = Unsafe.Add(ref searchSpace, index + 5);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found5;
+ lookUp = Unsafe.Add(ref searchSpace, index + 6);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found6;
+ lookUp = Unsafe.Add(ref searchSpace, index + 7);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found7;
+
+ index += 8;
+ }
+
+ if ((byte*)nLength >= (byte*)4)
+ {
+ nLength -= 4;
+
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found3;
+
+ index += 4;
+ }
+
+ while ((byte*)nLength > (byte*)0)
+ {
+ nLength -= 1;
+
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found;
+
+ index += 1;
+ }
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && ((int)(byte*)index < length))
+ {
+ nLength = (IntPtr)(uint)((length - (uint)index) & ~(Vector<byte>.Count - 1));
+ // Get comparison Vector
+ Vector<byte> values0 = GetVector(value0);
+ Vector<byte> values1 = GetVector(value1);
+
+ while ((byte*)nLength > (byte*)index)
+ {
+ Vector<byte> vData = Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index));
+ var vMatches = Vector.BitwiseOr(
+ Vector.Equals(vData, values0),
+ Vector.Equals(vData, values1));
+ if (Vector<byte>.Zero.Equals(vMatches))
+ {
+ index += Vector<byte>.Count;
+ continue;
+ }
+ // Find offset of first match
+ return (int)(byte*)index + LocateFirstFoundByte(vMatches);
+ }
+
+ if ((int)(byte*)index < length)
+ {
+ unchecked
+ {
+ nLength = (IntPtr)(length - (int)(byte*)index);
+ }
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ public static unsafe int IndexOfAny(ref byte searchSpace, byte value0, byte value1, byte value2, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ uint uValue2 = value2; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ IntPtr index = (IntPtr)0; // Use UIntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr nLength = (IntPtr)(uint)length;
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
+ {
+ unchecked
+ {
+ int unaligned = (int)(byte*)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
+ nLength = (IntPtr)(uint)((Vector<byte>.Count - unaligned) & (Vector<byte>.Count - 1));
+ }
+ }
+ SequentialScan:
+#endif
+ uint lookUp;
+ while ((byte*)nLength >= (byte*)8)
+ {
+ nLength -= 8;
+
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, index + 4);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found4;
+ lookUp = Unsafe.Add(ref searchSpace, index + 5);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found5;
+ lookUp = Unsafe.Add(ref searchSpace, index + 6);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found6;
+ lookUp = Unsafe.Add(ref searchSpace, index + 7);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found7;
+
+ index += 8;
+ }
+
+ if ((byte*)nLength >= (byte*)4)
+ {
+ nLength -= 4;
+
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found3;
+
+ index += 4;
+ }
+
+ while ((byte*)nLength > (byte*)0)
+ {
+ nLength -= 1;
+
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found;
+
+ index += 1;
+ }
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && ((int)(byte*)index < length))
+ {
+ nLength = (IntPtr)(uint)((length - (uint)index) & ~(Vector<byte>.Count - 1));
+ // Get comparison Vector
+ Vector<byte> values0 = GetVector(value0);
+ Vector<byte> values1 = GetVector(value1);
+ Vector<byte> values2 = GetVector(value2);
+ while ((byte*)nLength > (byte*)index)
+ {
+ Vector<byte> vData = Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index));
+
+ var vMatches = Vector.BitwiseOr(
+ Vector.BitwiseOr(
+ Vector.Equals(vData, values0),
+ Vector.Equals(vData, values1)),
+ Vector.Equals(vData, values2));
+
+ if (Vector<byte>.Zero.Equals(vMatches))
+ {
+ index += Vector<byte>.Count;
+ continue;
+ }
+ // Find offset of first match
+ return (int)(byte*)index + LocateFirstFoundByte(vMatches);
+ }
+
+ if ((int)(byte*)index < length)
+ {
+ unchecked
+ {
+ nLength = (IntPtr)(length - (int)(byte*)index);
+ }
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ public static unsafe int LastIndexOfAny(ref byte searchSpace, byte value0, byte value1, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ IntPtr index = (IntPtr)(uint)length; // Use UIntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr nLength = (IntPtr)(uint)length;
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
+ {
+ unchecked
+ {
+ int unaligned = (int)(byte*)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
+ nLength = (IntPtr)(((length & (Vector<byte>.Count - 1)) + unaligned) & (Vector<byte>.Count - 1));
+ }
+ }
+ SequentialScan:
+#endif
+ uint lookUp;
+ while ((byte*)nLength >= (byte*)8)
+ {
+ nLength -= 8;
+ index -= 8;
+
+ lookUp = Unsafe.Add(ref searchSpace, index + 7);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found7;
+ lookUp = Unsafe.Add(ref searchSpace, index + 6);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found6;
+ lookUp = Unsafe.Add(ref searchSpace, index + 5);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found5;
+ lookUp = Unsafe.Add(ref searchSpace, index + 4);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found4;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found;
+ }
+
+ if ((byte*)nLength >= (byte*)4)
+ {
+ nLength -= 4;
+ index -= 4;
+
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found;
+ }
+
+ while ((byte*)nLength > (byte*)0)
+ {
+ nLength -= 1;
+ index -= 1;
+
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found;
+ }
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && ((int)(byte*)index > 0))
+ {
+ nLength = (IntPtr)(uint)((uint)index & ~(Vector<byte>.Count - 1));
+ // Get comparison Vector
+ Vector<byte> values0 = GetVector(value0);
+ Vector<byte> values1 = GetVector(value1);
+
+ while ((byte*)nLength > (byte*)(Vector<byte>.Count - 1))
+ {
+ Vector<byte> vData = Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index - Vector<byte>.Count));
+ var vMatches = Vector.BitwiseOr(
+ Vector.Equals(vData, values0),
+ Vector.Equals(vData, values1));
+ if (Vector<byte>.Zero.Equals(vMatches))
+ {
+ index -= Vector<byte>.Count;
+ nLength -= Vector<byte>.Count;
+ continue;
+ }
+ // Find offset of first match
+ return (int)(byte*)(index) - Vector<byte>.Count + LocateLastFoundByte(vMatches);
+ }
+
+ if ((int)(byte*)index > 0)
+ {
+ nLength = index;
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ public static unsafe int LastIndexOfAny(ref byte searchSpace, byte value0, byte value1, byte value2, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ uint uValue2 = value2; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ IntPtr index = (IntPtr)(uint)length; // Use UIntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr nLength = (IntPtr)(uint)length;
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
+ {
+ unchecked
+ {
+ int unaligned = (int)(byte*)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
+ nLength = (IntPtr)(((length & (Vector<byte>.Count - 1)) + unaligned) & (Vector<byte>.Count - 1));
+ }
+ }
+ SequentialScan:
+#endif
+ uint lookUp;
+ while ((byte*)nLength >= (byte*)8)
+ {
+ nLength -= 8;
+ index -= 8;
+
+ lookUp = Unsafe.Add(ref searchSpace, index + 7);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found7;
+ lookUp = Unsafe.Add(ref searchSpace, index + 6);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found6;
+ lookUp = Unsafe.Add(ref searchSpace, index + 5);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found5;
+ lookUp = Unsafe.Add(ref searchSpace, index + 4);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found4;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found;
+ }
+
+ if ((byte*)nLength >= (byte*)4)
+ {
+ nLength -= 4;
+ index -= 4;
+
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found;
+ }
+
+ while ((byte*)nLength > (byte*)0)
+ {
+ nLength -= 1;
+ index -= 1;
+
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found;
+ }
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && ((int)(byte*)index > 0))
+ {
+ nLength = (IntPtr)(uint)((uint)index & ~(Vector<byte>.Count - 1));
+ // Get comparison Vector
+ Vector<byte> values0 = GetVector(value0);
+ Vector<byte> values1 = GetVector(value1);
+ Vector<byte> values2 = GetVector(value2);
+ while ((byte*)nLength > (byte*)(Vector<byte>.Count - 1))
+ {
+ Vector<byte> vData = Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index - Vector<byte>.Count));
+
+ var vMatches = Vector.BitwiseOr(
+ Vector.BitwiseOr(
+ Vector.Equals(vData, values0),
+ Vector.Equals(vData, values1)),
+ Vector.Equals(vData, values2));
+
+ if (Vector<byte>.Zero.Equals(vMatches))
+ {
+ index -= Vector<byte>.Count;
+ nLength -= Vector<byte>.Count;
+ continue;
+ }
+ // Find offset of first match
+ return (int)(byte*)(index) - Vector<byte>.Count + LocateLastFoundByte(vMatches);
+ }
+
+ if ((int)(byte*)index > 0)
+ {
+ nLength = index;
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ public static unsafe bool SequenceEqual(ref byte first, ref byte second, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ if (Unsafe.AreSame(ref first, ref second))
+ goto Equal;
+
+ IntPtr i = (IntPtr)0; // Use IntPtr and byte* for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr n = (IntPtr)length;
+
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && (byte*)n >= (byte*)Vector<byte>.Count)
+ {
+ n -= Vector<byte>.Count;
+ while ((byte*)n > (byte*)i)
+ {
+ if (Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref first, i)) !=
+ Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref second, i)))
+ {
+ goto NotEqual;
+ }
+ i += Vector<byte>.Count;
+ }
+ return Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref first, n)) ==
+ Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref second, n));
+ }
+#endif
+
+ if ((byte*)n >= (byte*)sizeof(UIntPtr))
+ {
+ n -= sizeof(UIntPtr);
+ while ((byte*)n > (byte*)i)
+ {
+ if (Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.AddByteOffset(ref first, i)) !=
+ Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.AddByteOffset(ref second, i)))
+ {
+ goto NotEqual;
+ }
+ i += sizeof(UIntPtr);
+ }
+ return Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.AddByteOffset(ref first, n)) ==
+ Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.AddByteOffset(ref second, n));
+ }
+
+ while ((byte*)n > (byte*)i)
+ {
+ if (Unsafe.AddByteOffset(ref first, i) != Unsafe.AddByteOffset(ref second, i))
+ goto NotEqual;
+ i += 1;
+ }
+
+ Equal:
+ return true;
+
+ NotEqual: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return false;
+ }
+
+#if !netstandard11
+ // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int LocateFirstFoundByte(Vector<byte> match)
+ {
+ var vector64 = Vector.AsVectorUInt64(match);
+ ulong candidate = 0;
+ int i = 0;
+ // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
+ for (; i < Vector<ulong>.Count; i++)
+ {
+ candidate = vector64[i];
+ if (candidate != 0)
+ {
+ break;
+ }
+ }
+
+ // Single LEA instruction with jitted const (using function result)
+ return i * 8 + LocateFirstFoundByte(candidate);
+ }
+#endif
+
+ public static unsafe int SequenceCompareTo(ref byte first, int firstLength, ref byte second, int secondLength)
+ {
+ Debug.Assert(firstLength >= 0);
+ Debug.Assert(secondLength >= 0);
+
+ if (Unsafe.AreSame(ref first, ref second))
+ goto Equal;
+
+ var minLength = firstLength;
+ if (minLength > secondLength) minLength = secondLength;
+
+ IntPtr i = (IntPtr)0; // Use IntPtr and byte* for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr n = (IntPtr)minLength;
+
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && (byte*)n > (byte*)Vector<byte>.Count)
+ {
+ n -= Vector<byte>.Count;
+ while ((byte*)n > (byte*)i)
+ {
+ if (Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref first, i)) !=
+ Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref second, i)))
+ {
+ goto NotEqual;
+ }
+ i += Vector<byte>.Count;
+ }
+ goto NotEqual;
+ }
+#endif
+
+ if ((byte*)n > (byte*)sizeof(UIntPtr))
+ {
+ n -= sizeof(UIntPtr);
+ while ((byte*)n > (byte*)i)
+ {
+ if (Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.AddByteOffset(ref first, i)) !=
+ Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.AddByteOffset(ref second, i)))
+ {
+ goto NotEqual;
+ }
+ i += sizeof(UIntPtr);
+ }
+ }
+
+ NotEqual: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ while((byte*)minLength > (byte*)i)
+ {
+ int result = Unsafe.AddByteOffset(ref first, i).CompareTo(Unsafe.AddByteOffset(ref second, i));
+ if (result != 0) return result;
+ i += 1;
+ }
+
+ Equal:
+ return firstLength - secondLength;
+ }
+
+#if !netstandard11
+ // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int LocateLastFoundByte(Vector<byte> match)
+ {
+ var vector64 = Vector.AsVectorUInt64(match);
+ ulong candidate = 0;
+ int i = Vector<ulong>.Count - 1;
+ // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
+ for (; i >= 0; i--)
+ {
+ candidate = vector64[i];
+ if (candidate != 0)
+ {
+ break;
+ }
+ }
+
+ // Single LEA instruction with jitted const (using function result)
+ return i * 8 + LocateLastFoundByte(candidate);
+ }
+#endif
+
+#if !netstandard11
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int LocateFirstFoundByte(ulong match)
+ {
+ unchecked
+ {
+ // Flag least significant power of two bit
+ var powerOfTwoFlag = match ^ (match - 1);
+ // Shift all powers of two into the high byte and extract
+ return (int)((powerOfTwoFlag * XorPowerOfTwoToHighByte) >> 57);
+ }
+ }
+#endif
+
+#if !netstandard11
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int LocateLastFoundByte(ulong match)
+ {
+ // Find the most significant byte that has its highest bit set
+ int index = 7;
+ while ((long)match > 0)
+ {
+ match = match << 8;
+ index--;
+ }
+ return index;
+ }
+#endif
+
+#if !netstandard11
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector<byte> GetVector(byte vectorByte)
+ {
+#if !netcoreapp
+ // Vector<byte> .ctor doesn't become an intrinsic due to detection issue
+ // However this does cause it to become an intrinsic (with additional multiply and reg->reg copy)
+ // https://github.com/dotnet/coreclr/issues/7459#issuecomment-253965670
+ return Vector.AsVectorByte(new Vector<uint>(vectorByte * 0x01010101u));
+#else
+ return new Vector<byte>(vectorByte);
+#endif
+ }
+#endif
+
+#if !netstandard11
+ private const ulong XorPowerOfTwoToHighByte = (0x07ul |
+ 0x06ul << 8 |
+ 0x05ul << 16 |
+ 0x04ul << 24 |
+ 0x03ul << 32 |
+ 0x02ul << 40 |
+ 0x01ul << 48) + 1;
+#endif
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/SpanHelpers.T.cs b/src/System.Private.CoreLib/shared/System/SpanHelpers.T.cs
new file mode 100644
index 000000000..d1c62c8d5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/SpanHelpers.T.cs
@@ -0,0 +1,683 @@
+// 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;
+
+#if !netstandard
+using Internal.Runtime.CompilerServices;
+#else
+using System.Runtime.CompilerServices;
+#endif
+
+namespace System
+{
+ internal static partial class SpanHelpers
+ {
+ public static int IndexOf<T>(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ T valueHead = value;
+ ref T valueTail = ref Unsafe.Add(ref value, 1);
+ int valueTailLength = valueLength - 1;
+
+ int index = 0;
+ for (; ; )
+ {
+ Debug.Assert(0 <= index && index <= searchSpaceLength); // Ensures no deceptive underflows in the computation of "remainingSearchSpaceLength".
+ int remainingSearchSpaceLength = searchSpaceLength - index - valueTailLength;
+ if (remainingSearchSpaceLength <= 0)
+ break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there.
+
+ // Do a quick search for the first element of "value".
+ int relativeIndex = IndexOf(ref Unsafe.Add(ref searchSpace, index), valueHead, remainingSearchSpaceLength);
+ if (relativeIndex == -1)
+ break;
+ index += relativeIndex;
+
+ // Found the first element of "value". See if the tail matches.
+ if (SequenceEqual(ref Unsafe.Add(ref searchSpace, index + 1), ref valueTail, valueTailLength))
+ return index; // The tail matched. Return a successful find.
+
+ index++;
+ }
+ return -1;
+ }
+
+ public static unsafe int IndexOf<T>(ref T searchSpace, T value, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ IntPtr index = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ while (length >= 8)
+ {
+ length -= 8;
+
+ if (value.Equals(Unsafe.Add(ref searchSpace, index)))
+ goto Found;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 1)))
+ goto Found1;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 2)))
+ goto Found2;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 3)))
+ goto Found3;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 4)))
+ goto Found4;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 5)))
+ goto Found5;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 6)))
+ goto Found6;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 7)))
+ goto Found7;
+
+ index += 8;
+ }
+
+ if (length >= 4)
+ {
+ length -= 4;
+
+ if (value.Equals(Unsafe.Add(ref searchSpace, index)))
+ goto Found;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 1)))
+ goto Found1;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 2)))
+ goto Found2;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 3)))
+ goto Found3;
+
+ index += 4;
+ }
+
+ while (length > 0)
+ {
+ if (value.Equals(Unsafe.Add(ref searchSpace, index)))
+ goto Found;
+
+ index += 1;
+ length--;
+ }
+ return -1;
+
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ public static int IndexOfAny<T>(ref T searchSpace, T value0, T value1, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ T lookUp;
+ int index = 0;
+ while ((length - index) >= 8)
+ {
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, index + 4);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found4;
+ lookUp = Unsafe.Add(ref searchSpace, index + 5);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found5;
+ lookUp = Unsafe.Add(ref searchSpace, index + 6);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found6;
+ lookUp = Unsafe.Add(ref searchSpace, index + 7);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found7;
+
+ index += 8;
+ }
+
+ if ((length - index) >= 4)
+ {
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found3;
+
+ index += 4;
+ }
+
+ while (index < length)
+ {
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found;
+
+ index++;
+ }
+ return -1;
+
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return index;
+ Found1:
+ return index + 1;
+ Found2:
+ return index + 2;
+ Found3:
+ return index + 3;
+ Found4:
+ return index + 4;
+ Found5:
+ return index + 5;
+ Found6:
+ return index + 6;
+ Found7:
+ return index + 7;
+ }
+
+ public static int IndexOfAny<T>(ref T searchSpace, T value0, T value1, T value2, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ T lookUp;
+ int index = 0;
+ while ((length - index) >= 8)
+ {
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, index + 4);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found4;
+ lookUp = Unsafe.Add(ref searchSpace, index + 5);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found5;
+ lookUp = Unsafe.Add(ref searchSpace, index + 6);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found6;
+ lookUp = Unsafe.Add(ref searchSpace, index + 7);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found7;
+
+ index += 8;
+ }
+
+ if ((length - index) >= 4)
+ {
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found3;
+
+ index += 4;
+ }
+
+ while (index < length)
+ {
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found;
+
+ index++;
+ }
+ return -1;
+
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return index;
+ Found1:
+ return index + 1;
+ Found2:
+ return index + 2;
+ Found3:
+ return index + 3;
+ Found4:
+ return index + 4;
+ Found5:
+ return index + 5;
+ Found6:
+ return index + 6;
+ Found7:
+ return index + 7;
+ }
+
+ public static int IndexOfAny<T>(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ int index = -1;
+ for (int i = 0; i < valueLength; i++)
+ {
+ var tempIndex = IndexOf(ref searchSpace, Unsafe.Add(ref value, i), searchSpaceLength);
+ if ((uint)tempIndex < (uint)index)
+ {
+ index = tempIndex;
+ // Reduce space for search, cause we don't care if we find the search value after the index of a previously found value
+ searchSpaceLength = tempIndex;
+
+ if (index == 0) break;
+ }
+ }
+ return index;
+ }
+
+ public static int LastIndexOf<T>(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ T valueHead = value;
+ ref T valueTail = ref Unsafe.Add(ref value, 1);
+ int valueTailLength = valueLength - 1;
+
+ int index = 0;
+ for (; ; )
+ {
+ Debug.Assert(0 <= index && index <= searchSpaceLength); // Ensures no deceptive underflows in the computation of "remainingSearchSpaceLength".
+ int remainingSearchSpaceLength = searchSpaceLength - index - valueTailLength;
+ if (remainingSearchSpaceLength <= 0)
+ break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there.
+
+ // Do a quick search for the first element of "value".
+ int relativeIndex = LastIndexOf(ref searchSpace, valueHead, remainingSearchSpaceLength);
+ if (relativeIndex == -1)
+ break;
+
+ // Found the first element of "value". See if the tail matches.
+ if (SequenceEqual(ref Unsafe.Add(ref searchSpace, relativeIndex + 1), ref valueTail, valueTailLength))
+ return relativeIndex; // The tail matched. Return a successful find.
+
+ index += remainingSearchSpaceLength - relativeIndex;
+ }
+ return -1;
+ }
+
+ public static int LastIndexOf<T>(ref T searchSpace, T value, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ while (length >= 8)
+ {
+ length -= 8;
+
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 7)))
+ goto Found7;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 6)))
+ goto Found6;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 5)))
+ goto Found5;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 4)))
+ goto Found4;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 3)))
+ goto Found3;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 2)))
+ goto Found2;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 1)))
+ goto Found1;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length)))
+ goto Found;
+ }
+
+ if (length >= 4)
+ {
+ length -= 4;
+
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 3)))
+ goto Found3;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 2)))
+ goto Found2;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 1)))
+ goto Found1;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length)))
+ goto Found;
+ }
+
+ while (length > 0)
+ {
+ length--;
+
+ if (value.Equals(Unsafe.Add(ref searchSpace, length)))
+ goto Found;
+ }
+ return -1;
+
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return length;
+ Found1:
+ return length + 1;
+ Found2:
+ return length + 2;
+ Found3:
+ return length + 3;
+ Found4:
+ return length + 4;
+ Found5:
+ return length + 5;
+ Found6:
+ return length + 6;
+ Found7:
+ return length + 7;
+ }
+
+ public static int LastIndexOfAny<T>(ref T searchSpace, T value0, T value1, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ T lookUp;
+ while (length >= 8)
+ {
+ length -= 8;
+
+ lookUp = Unsafe.Add(ref searchSpace, length + 7);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found7;
+ lookUp = Unsafe.Add(ref searchSpace, length + 6);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found6;
+ lookUp = Unsafe.Add(ref searchSpace, length + 5);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found5;
+ lookUp = Unsafe.Add(ref searchSpace, length + 4);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found4;
+ lookUp = Unsafe.Add(ref searchSpace, length + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, length + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, length + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, length);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found;
+ }
+
+ if (length >= 4)
+ {
+ length -= 4;
+
+ lookUp = Unsafe.Add(ref searchSpace, length + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, length + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, length + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, length);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found;
+ }
+
+ while (length > 0)
+ {
+ length--;
+
+ lookUp = Unsafe.Add(ref searchSpace, length);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found;
+ }
+ return -1;
+
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return length;
+ Found1:
+ return length + 1;
+ Found2:
+ return length + 2;
+ Found3:
+ return length + 3;
+ Found4:
+ return length + 4;
+ Found5:
+ return length + 5;
+ Found6:
+ return length + 6;
+ Found7:
+ return length + 7;
+ }
+
+ public static int LastIndexOfAny<T>(ref T searchSpace, T value0, T value1, T value2, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ T lookUp;
+ while (length >= 8)
+ {
+ length -= 8;
+
+ lookUp = Unsafe.Add(ref searchSpace, length + 7);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found7;
+ lookUp = Unsafe.Add(ref searchSpace, length + 6);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found6;
+ lookUp = Unsafe.Add(ref searchSpace, length + 5);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found5;
+ lookUp = Unsafe.Add(ref searchSpace, length + 4);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found4;
+ lookUp = Unsafe.Add(ref searchSpace, length + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, length + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, length + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, length);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found;
+ }
+
+ if (length >= 4)
+ {
+ length -= 4;
+
+ lookUp = Unsafe.Add(ref searchSpace, length + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, length + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, length + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, length);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found;
+ }
+
+ while (length > 0)
+ {
+ length--;
+
+ lookUp = Unsafe.Add(ref searchSpace, length);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found;
+ }
+ return -1;
+
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return length;
+ Found1:
+ return length + 1;
+ Found2:
+ return length + 2;
+ Found3:
+ return length + 3;
+ Found4:
+ return length + 4;
+ Found5:
+ return length + 5;
+ Found6:
+ return length + 6;
+ Found7:
+ return length + 7;
+ }
+
+ public static int LastIndexOfAny<T>(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ int index = -1;
+ for (int i = 0; i < valueLength; i++)
+ {
+ var tempIndex = LastIndexOf(ref searchSpace, Unsafe.Add(ref value, i), searchSpaceLength);
+ if (tempIndex > index) index = tempIndex;
+ }
+ return index;
+ }
+
+ public static bool SequenceEqual<T>(ref T first, ref T second, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ if (Unsafe.AreSame(ref first, ref second))
+ goto Equal;
+
+ IntPtr index = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ while (length >= 8)
+ {
+ length -= 8;
+
+ if (!Unsafe.Add(ref first, index).Equals(Unsafe.Add(ref second, index)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 1).Equals(Unsafe.Add(ref second, index + 1)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 2).Equals(Unsafe.Add(ref second, index + 2)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 3).Equals(Unsafe.Add(ref second, index + 3)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 4).Equals(Unsafe.Add(ref second, index + 4)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 5).Equals(Unsafe.Add(ref second, index + 5)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 6).Equals(Unsafe.Add(ref second, index + 6)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 7).Equals(Unsafe.Add(ref second, index + 7)))
+ goto NotEqual;
+
+ index += 8;
+ }
+
+ if (length >= 4)
+ {
+ length -= 4;
+
+ if (!Unsafe.Add(ref first, index).Equals(Unsafe.Add(ref second, index)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 1).Equals(Unsafe.Add(ref second, index + 1)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 2).Equals(Unsafe.Add(ref second, index + 2)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 3).Equals(Unsafe.Add(ref second, index + 3)))
+ goto NotEqual;
+
+ index += 4;
+ }
+
+ while (length > 0)
+ {
+ if (!Unsafe.Add(ref first, index).Equals(Unsafe.Add(ref second, index)))
+ goto NotEqual;
+ index += 1;
+ length--;
+ }
+
+ Equal:
+ return true;
+
+ NotEqual: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return false;
+ }
+
+ public static int SequenceCompareTo<T>(ref T first, int firstLength, ref T second, int secondLength)
+ where T : IComparable<T>
+ {
+ Debug.Assert(firstLength >= 0);
+ Debug.Assert(secondLength >= 0);
+
+ var minLength = firstLength;
+ if (minLength > secondLength) minLength = secondLength;
+ for (int i = 0; i < minLength; i++)
+ {
+ int result = Unsafe.Add(ref first, i).CompareTo(Unsafe.Add(ref second, i));
+ if (result != 0) return result;
+ }
+ return firstLength.CompareTo(secondLength);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/SpanHelpers.cs b/src/System.Private.CoreLib/shared/System/SpanHelpers.cs
new file mode 100644
index 000000000..dad0f6294
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/SpanHelpers.cs
@@ -0,0 +1,503 @@
+// 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.Globalization;
+using System.Runtime;
+using System.Runtime.InteropServices;
+
+using Internal.Runtime.CompilerServices;
+
+#if BIT64
+using nuint = System.UInt64;
+#else
+using nuint = System.UInt32;
+#endif
+
+namespace System
+{
+ internal static partial class SpanHelpers
+ {
+ public static int IndexOfCultureHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, CompareInfo compareInfo)
+ {
+ Debug.Assert(span.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ if (GlobalizationMode.Invariant)
+ {
+ return CompareInfo.InvariantIndexOf(span, value, ignoreCase: false);
+ }
+
+ return compareInfo.IndexOf(span, value, CompareOptions.None);
+ }
+
+ public static int IndexOfCultureIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, CompareInfo compareInfo)
+ {
+ Debug.Assert(span.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ if (GlobalizationMode.Invariant)
+ {
+ return CompareInfo.InvariantIndexOf(span, value, ignoreCase: true);
+ }
+
+ return compareInfo.IndexOf(span, value, CompareOptions.IgnoreCase);
+ }
+
+ public static int IndexOfOrdinalHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(span.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ if (GlobalizationMode.Invariant)
+ {
+ return CompareInfo.InvariantIndexOf(span, value, ignoreCase);
+ }
+
+ return CompareInfo.Invariant.IndexOfOrdinal(span, value, ignoreCase);
+ }
+
+ public static bool StartsWithCultureHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, CompareInfo compareInfo)
+ {
+ Debug.Assert(value.Length != 0);
+
+ if (GlobalizationMode.Invariant)
+ {
+ return span.StartsWith(value);
+ }
+ if (span.Length == 0)
+ {
+ return false;
+ }
+ return compareInfo.IsPrefix(span, value, CompareOptions.None);
+ }
+
+ public static bool StartsWithCultureIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, CompareInfo compareInfo)
+ {
+ Debug.Assert(value.Length != 0);
+
+ if (GlobalizationMode.Invariant)
+ {
+ return StartsWithOrdinalIgnoreCaseHelper(span, value);
+ }
+ if (span.Length == 0)
+ {
+ return false;
+ }
+ return compareInfo.IsPrefix(span, value, CompareOptions.IgnoreCase);
+ }
+
+ public static bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
+ {
+ Debug.Assert(value.Length != 0);
+
+ if (span.Length < value.Length)
+ {
+ return false;
+ }
+ return CompareInfo.CompareOrdinalIgnoreCase(span.Slice(0, value.Length), value) == 0;
+ }
+
+ public static bool EndsWithCultureHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, CompareInfo compareInfo)
+ {
+ Debug.Assert(value.Length != 0);
+
+ if (GlobalizationMode.Invariant)
+ {
+ return span.EndsWith(value);
+ }
+ if (span.Length == 0)
+ {
+ return false;
+ }
+ return compareInfo.IsSuffix(span, value, CompareOptions.None);
+ }
+
+ public static bool EndsWithCultureIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, CompareInfo compareInfo)
+ {
+ Debug.Assert(value.Length != 0);
+
+ if (GlobalizationMode.Invariant)
+ {
+ return EndsWithOrdinalIgnoreCaseHelper(span, value);
+ }
+ if (span.Length == 0)
+ {
+ return false;
+ }
+ return compareInfo.IsSuffix(span, value, CompareOptions.IgnoreCase);
+ }
+
+ public static bool EndsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
+ {
+ Debug.Assert(value.Length != 0);
+
+ if (span.Length < value.Length)
+ {
+ return false;
+ }
+ return (CompareInfo.CompareOrdinalIgnoreCase(span.Slice(span.Length - value.Length), value) == 0);
+ }
+
+ public static unsafe void ClearWithoutReferences(ref byte b, nuint byteLength)
+ {
+ if (byteLength == 0)
+ return;
+
+#if CORECLR && (AMD64 || ARM64)
+ if (byteLength > 4096)
+ goto PInvoke;
+ Unsafe.InitBlockUnaligned(ref b, 0, (uint)byteLength);
+ return;
+#else
+ // TODO: Optimize other platforms to be on par with AMD64 CoreCLR
+ // Note: It's important that this switch handles lengths at least up to 22.
+ // See notes below near the main loop for why.
+
+ // The switch will be very fast since it can be implemented using a jump
+ // table in assembly. See http://stackoverflow.com/a/449297/4077294 for more info.
+
+ switch (byteLength)
+ {
+ case 1:
+ b = 0;
+ return;
+ case 2:
+ Unsafe.As<byte, short>(ref b) = 0;
+ return;
+ case 3:
+ Unsafe.As<byte, short>(ref b) = 0;
+ Unsafe.Add<byte>(ref b, 2) = 0;
+ return;
+ case 4:
+ Unsafe.As<byte, int>(ref b) = 0;
+ return;
+ case 5:
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.Add<byte>(ref b, 4) = 0;
+ return;
+ case 6:
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ return;
+ case 7:
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.Add<byte>(ref b, 6) = 0;
+ return;
+ case 8:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ return;
+ case 9:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.Add<byte>(ref b, 8) = 0;
+ return;
+ case 10:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ return;
+ case 11:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.Add<byte>(ref b, 10) = 0;
+ return;
+ case 12:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ return;
+ case 13:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.Add<byte>(ref b, 12) = 0;
+ return;
+ case 14:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+ return;
+ case 15:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+ Unsafe.Add<byte>(ref b, 14) = 0;
+ return;
+ case 16:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ return;
+ case 17:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ Unsafe.Add<byte>(ref b, 16) = 0;
+ return;
+ case 18:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
+ return;
+ case 19:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
+ Unsafe.Add<byte>(ref b, 18) = 0;
+ return;
+ case 20:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
+ return;
+ case 21:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
+ Unsafe.Add<byte>(ref b, 20) = 0;
+ return;
+ case 22:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 20)) = 0;
+ return;
+ }
+
+ // P/Invoke into the native version for large lengths
+ if (byteLength >= 512) goto PInvoke;
+
+ nuint i = 0; // byte offset at which we're copying
+
+ if ((Unsafe.As<byte, int>(ref b) & 3) != 0)
+ {
+ if ((Unsafe.As<byte, int>(ref b) & 1) != 0)
+ {
+ Unsafe.AddByteOffset<byte>(ref b, i) = 0;
+ i += 1;
+ if ((Unsafe.As<byte, int>(ref b) & 2) != 0)
+ goto IntAligned;
+ }
+ Unsafe.As<byte, short>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ i += 2;
+ }
+
+ IntAligned:
+
+ // On 64-bit IntPtr.Size == 8, so we want to advance to the next 8-aligned address. If
+ // (int)b % 8 is 0, 5, 6, or 7, we will already have advanced by 0, 3, 2, or 1
+ // bytes to the next aligned address (respectively), so do nothing. On the other hand,
+ // if it is 1, 2, 3, or 4 we will want to copy-and-advance another 4 bytes until
+ // we're aligned.
+ // The thing 1, 2, 3, and 4 have in common that the others don't is that if you
+ // subtract one from them, their 3rd lsb will not be set. Hence, the below check.
+
+ if (((Unsafe.As<byte, int>(ref b) - 1) & 4) == 0)
+ {
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ i += 4;
+ }
+
+ nuint end = byteLength - 16;
+ byteLength -= i; // lower 4 bits of byteLength represent how many bytes are left *after* the unrolled loop
+
+ // We know due to the above switch-case that this loop will always run 1 iteration; max
+ // bytes we clear before checking is 23 (7 to align the pointers, 16 for 1 iteration) so
+ // the switch handles lengths 0-22.
+ Debug.Assert(end >= 7 && i <= end);
+
+ // This is separated out into a different variable, so the i + 16 addition can be
+ // performed at the start of the pipeline and the loop condition does not have
+ // a dependency on the writes.
+ nuint counter;
+
+ do
+ {
+ counter = i + 16;
+
+ // This loop looks very costly since there appear to be a bunch of temporary values
+ // being created with the adds, but the jit (for x86 anyways) will convert each of
+ // these to use memory addressing operands.
+
+ // So the only cost is a bit of code size, which is made up for by the fact that
+ // we save on writes to b.
+
+#if BIT64
+ Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i + 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 12)) = 0;
+#endif
+
+ i = counter;
+
+ // See notes above for why this wasn't used instead
+ // i += 16;
+ }
+ while (counter <= end);
+
+ if ((byteLength & 8) != 0)
+ {
+#if BIT64
+ Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+#else
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 4)) = 0;
+#endif
+ i += 8;
+ }
+ if ((byteLength & 4) != 0)
+ {
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ i += 4;
+ }
+ if ((byteLength & 2) != 0)
+ {
+ Unsafe.As<byte, short>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ i += 2;
+ }
+ if ((byteLength & 1) != 0)
+ {
+ Unsafe.AddByteOffset<byte>(ref b, i) = 0;
+ // We're not using i after this, so not needed
+ // i += 1;
+ }
+
+ return;
+#endif
+
+ PInvoke:
+ RuntimeImports.RhZeroMemory(ref b, byteLength);
+ }
+
+ public static unsafe void ClearWithReferences(ref IntPtr ip, nuint pointerSizeLength)
+ {
+ if (pointerSizeLength == 0)
+ return;
+
+ // TODO: Perhaps do switch casing to improve small size perf
+
+ nuint i = 0;
+ nuint n = 0;
+ while ((n = i + 8) <= (pointerSizeLength))
+ {
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 1) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 2) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 3) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 4) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 5) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 6) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 7) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ i = n;
+ }
+ if ((n = i + 4) <= (pointerSizeLength))
+ {
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 1) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 2) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 3) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ i = n;
+ }
+ if ((n = i + 2) <= (pointerSizeLength))
+ {
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 1) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ i = n;
+ }
+ if ((i + 1) <= (pointerSizeLength))
+ {
+ Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr);
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/String.Manipulation.cs b/src/System.Private.CoreLib/shared/System/String.Manipulation.cs
index 1d500e8d5..76cbfaa8d 100644
--- a/src/System.Private.CoreLib/shared/System/String.Manipulation.cs
+++ b/src/System.Private.CoreLib/shared/System/String.Manipulation.cs
@@ -1107,7 +1107,7 @@ namespace System
// String allocation and copying is in separate method to make this method faster for the case where
// nothing needs replacing.
- string dst = ReplaceHelper(oldValue.Length, newValue, replacementIndices.AsReadOnlySpan());
+ string dst = ReplaceHelper(oldValue.Length, newValue, replacementIndices.AsSpan());
replacementIndices.Dispose();
@@ -1136,19 +1136,19 @@ namespace System
int count = replacementIdx - thisIdx;
if (count != 0)
{
- this.AsReadOnlySpan().Slice(thisIdx, count).CopyTo(dstSpan.Slice(dstIdx));
+ this.AsSpan().Slice(thisIdx, count).CopyTo(dstSpan.Slice(dstIdx));
dstIdx += count;
}
thisIdx = replacementIdx + oldValueLength;
// Copy over newValue to replace the oldValue.
- newValue.AsReadOnlySpan().CopyTo(dstSpan.Slice(dstIdx));
+ newValue.AsSpan().CopyTo(dstSpan.Slice(dstIdx));
dstIdx += newValue.Length;
}
// Copy over the final non-matching portion at the end of the string.
Debug.Assert(this.Length - thisIdx == dstSpan.Length - dstIdx);
- this.AsReadOnlySpan().Slice(thisIdx).CopyTo(dstSpan.Slice(dstIdx));
+ this.AsSpan().Slice(thisIdx).CopyTo(dstSpan.Slice(dstIdx));
return dst;
}
@@ -1228,7 +1228,7 @@ namespace System
var sepListBuilder = new ValueListBuilder<int>(initialSpan);
MakeSeparatorList(separators, ref sepListBuilder);
- ReadOnlySpan<int> sepList = sepListBuilder.AsReadOnlySpan();
+ ReadOnlySpan<int> sepList = sepListBuilder.AsSpan();
// Handle the special case of no replaces.
if (sepList.Length == 0)
@@ -1309,8 +1309,8 @@ namespace System
var lengthListBuilder = new ValueListBuilder<int>(lengthListInitialSpan);
MakeSeparatorList(separators, ref sepListBuilder, ref lengthListBuilder);
- ReadOnlySpan<int> sepList = sepListBuilder.AsReadOnlySpan();
- ReadOnlySpan<int> lengthList = lengthListBuilder.AsReadOnlySpan();
+ ReadOnlySpan<int> sepList = sepListBuilder.AsSpan();
+ ReadOnlySpan<int> lengthList = lengthListBuilder.AsSpan();
// Handle the special case of no replaces.
if (sepList.Length == 0)
@@ -1334,7 +1334,7 @@ namespace System
var sepListBuilder = new ValueListBuilder<int>(sepListInitialSpan);
MakeSeparatorList(separator, ref sepListBuilder);
- ReadOnlySpan<int> sepList = sepListBuilder.AsReadOnlySpan();
+ ReadOnlySpan<int> sepList = sepListBuilder.AsSpan();
if (sepList.Length == 0)
{
// there are no separators so sepListBuilder did not rent an array from pool and there is no need to dispose it
diff --git a/src/System.Private.CoreLib/shared/System/StringSpanHelpers.cs b/src/System.Private.CoreLib/shared/System/StringSpanHelpers.cs
index 7419adbf0..2d6152de5 100644
--- a/src/System.Private.CoreLib/shared/System/StringSpanHelpers.cs
+++ b/src/System.Private.CoreLib/shared/System/StringSpanHelpers.cs
@@ -17,7 +17,7 @@ namespace System
throw new ArgumentOutOfRangeException(nameof(comparisonType));
public static bool Equals(this ReadOnlySpan<char> left, string right) =>
- Equals(left, right.AsReadOnlySpan());
+ Equals(left, right.AsSpan());
public static bool Equals(this ReadOnlySpan<char> left, ReadOnlySpan<char> right)
{
@@ -57,23 +57,6 @@ namespace System
return true;
}
- public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> source)
- {
- int startIndex = 0, endIndex = source.Length - 1;
-
- while (startIndex <= endIndex && char.IsWhiteSpace(source[startIndex]))
- {
- startIndex++;
- }
-
- while (endIndex >= startIndex && char.IsWhiteSpace(source[endIndex]))
- {
- endIndex--;
- }
-
- return source.Slice(startIndex, endIndex - startIndex + 1);
- }
-
public static int IndexOf(this ReadOnlySpan<char> source, char value) =>
IndexOf(source, value, 0);
diff --git a/src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs b/src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs
index 19c15498a..a7804c399 100644
--- a/src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs
+++ b/src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs
@@ -1552,7 +1552,7 @@ namespace System.Text
if (startPos != pos)
{
// There was no brace escaping, extract the item format as a single string
- itemFormatSpan = format.AsReadOnlySpan().Slice(startPos, pos - startPos);
+ itemFormatSpan = format.AsSpan().Slice(startPos, pos - startPos);
}
}
else
diff --git a/src/System.Private.CoreLib/shared/System/Text/ValueStringBuilder.cs b/src/System.Private.CoreLib/shared/System/Text/ValueStringBuilder.cs
index e077a3423..18d564867 100644
--- a/src/System.Private.CoreLib/shared/System/Text/ValueStringBuilder.cs
+++ b/src/System.Private.CoreLib/shared/System/Text/ValueStringBuilder.cs
@@ -5,6 +5,7 @@
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace System.Text
{
@@ -21,7 +22,25 @@ namespace System.Text
_pos = 0;
}
- public int Length => _pos;
+ public int Length
+ {
+ get => _pos;
+ set
+ {
+ Debug.Assert(value <= _chars.Length);
+ _pos = value;
+ }
+ }
+
+ public int Capacity => _chars.Length;
+
+ public void EnsureCapacity(int capacity)
+ {
+ if (capacity > _chars.Length)
+ Grow(capacity - _chars.Length);
+ }
+
+ public ref char GetPinnableReference() => ref MemoryMarshal.GetReference(_chars);
public ref char this[int index]
{
@@ -39,6 +58,8 @@ namespace System.Text
return s;
}
+ public ReadOnlySpan<char> AsSpan() => _chars.Slice(0, _pos);
+
public bool TryCopyTo(Span<char> destination, out int charsWritten)
{
if (_chars.Slice(0, _pos).TryCopyTo(destination))
@@ -106,7 +127,7 @@ namespace System.Text
Grow(s.Length);
}
- s.AsReadOnlySpan().CopyTo(_chars.Slice(pos));
+ s.AsSpan().CopyTo(_chars.Slice(pos));
_pos += s.Length;
}
@@ -141,6 +162,18 @@ namespace System.Text
_pos += length;
}
+ public unsafe void Append(ReadOnlySpan<char> value)
+ {
+ int pos = _pos;
+ if (pos > _chars.Length - value.Length)
+ {
+ Grow(value.Length);
+ }
+
+ value.CopyTo(_chars.Slice(_pos));
+ _pos += value.Length;
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<char> AppendSpan(int length)
{
@@ -164,7 +197,7 @@ namespace System.Text
[MethodImpl(MethodImplOptions.NoInlining)]
private void Grow(int requiredAdditionalCapacity)
{
- Debug.Assert(requiredAdditionalCapacity > _chars.Length - _pos);
+ Debug.Assert(requiredAdditionalCapacity > 0);
char[] poolArray = ArrayPool<char>.Shared.Rent(Math.Max(_pos + requiredAdditionalCapacity, _chars.Length * 2));
diff --git a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs
index a06545fab..410eaf3ff 100644
--- a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs
+++ b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs
@@ -1198,9 +1198,9 @@ namespace System
int secondDotIndex = dateRule.IndexOf('.', firstDotIndex + 1);
if (secondDotIndex > 0)
{
- if (int.TryParse(dateRule.AsReadOnlySpan().Slice(1, firstDotIndex - 1), out month) &&
- int.TryParse(dateRule.AsReadOnlySpan().Slice(firstDotIndex + 1, secondDotIndex - firstDotIndex - 1), out week) &&
- int.TryParse(dateRule.AsReadOnlySpan().Slice(secondDotIndex + 1), out int day))
+ if (int.TryParse(dateRule.AsSpan().Slice(1, firstDotIndex - 1), out month) &&
+ int.TryParse(dateRule.AsSpan().Slice(firstDotIndex + 1, secondDotIndex - firstDotIndex - 1), out week) &&
+ int.TryParse(dateRule.AsSpan().Slice(secondDotIndex + 1), out int day))
{
dayOfWeek = (DayOfWeek)day;
return true;
diff --git a/src/System.Private.CoreLib/shared/System/Version.cs b/src/System.Private.CoreLib/shared/System/Version.cs
index df16be2cd..9e4cefcd6 100644
--- a/src/System.Private.CoreLib/shared/System/Version.cs
+++ b/src/System.Private.CoreLib/shared/System/Version.cs
@@ -300,7 +300,7 @@ namespace System
throw new ArgumentNullException(nameof(input));
}
- return ParseVersion(input.AsReadOnlySpan(), throwOnFailure: true);
+ return ParseVersion(input.AsSpan(), throwOnFailure: true);
}
public static Version Parse(ReadOnlySpan<char> input) =>
@@ -314,7 +314,7 @@ namespace System
return false;
}
- return (result = ParseVersion(input.AsReadOnlySpan(), throwOnFailure: false)) != null;
+ return (result = ParseVersion(input.AsSpan(), throwOnFailure: false)) != null;
}
public static bool TryParse(ReadOnlySpan<char> input, out Version result) =>
diff --git a/src/System.Private.CoreLib/src/Resources/Strings.resx b/src/System.Private.CoreLib/src/Resources/Strings.resx
index fd003daae..2eedd405c 100644
--- a/src/System.Private.CoreLib/src/Resources/Strings.resx
+++ b/src/System.Private.CoreLib/src/Resources/Strings.resx
@@ -2611,6 +2611,9 @@
<data name="Argument_InvalidTypeWithPointersNotSupported" xml:space="preserve">
<value>Cannot use type '{0}'. Only value types without pointers or references are supported.</value>
</data>
+ <data name="Argument_OverlapAlignmentMismatch" xml:space="preserve">
+ <value>Overlapping spans have mismatching alignment.</value>
+ </data>
<data name="ArrayTypeMismatch_ConstrainedCopy" xml:space="preserve">
<value>Array.ConstrainedCopy will only work on array types that are provably compatible, without any form of boxing, unboxing, widening, or casting of each array element. Change the array types (i.e., copy a Derived[] to a Base[]), or use a mitigation strategy in the CER for Array.Copy's less powerful reliability contract, such as cloning the array or throwing away the potentially corrupt destination array.</value>
</data>
diff --git a/src/System.Private.CoreLib/src/System/Globalization/CompareInfo.Unix.cs b/src/System.Private.CoreLib/src/System/Globalization/CompareInfo.Unix.cs
index 501e80457..40f0ab008 100644
--- a/src/System.Private.CoreLib/src/System/Globalization/CompareInfo.Unix.cs
+++ b/src/System.Private.CoreLib/src/System/Globalization/CompareInfo.Unix.cs
@@ -87,6 +87,47 @@ namespace System.Globalization
return -1;
}
+ internal static unsafe int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ if (source.Length < value.Length)
+ {
+ return -1;
+ }
+
+ if (ignoreCase)
+ {
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pValue = &MemoryMarshal.GetReference(value))
+ {
+ int index = Interop.Globalization.IndexOfOrdinalIgnoreCase(pValue, value.Length, pSource, source.Length, findLast: false);
+ return index;
+ }
+ }
+
+ int endIndex = source.Length - value.Length;
+ for (int i = 0; i <= endIndex; i++)
+ {
+ int valueIndex, sourceIndex;
+
+ for (valueIndex = 0, sourceIndex = i;
+ valueIndex < value.Length && source[sourceIndex] == value[valueIndex];
+ valueIndex++, sourceIndex++)
+ ;
+
+ if (valueIndex == value.Length)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
internal static unsafe int LastIndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
{
Debug.Assert(!GlobalizationMode.Invariant);
@@ -218,6 +259,159 @@ namespace System.Globalization
}
}
+ // For now, this method is only called from Span APIs with either options == CompareOptions.None or CompareOptions.IgnoreCase
+ internal unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(target.Length != 0);
+
+ if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
+ {
+ if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
+ {
+ return IndexOfOrdinalIgnoreCaseHelper(source, target, options, matchLengthPtr);
+ }
+ else
+ {
+ return IndexOfOrdinalHelper(source, target, options, matchLengthPtr);
+ }
+ }
+ else
+ {
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pTarget = &MemoryMarshal.GetReference(target))
+ {
+ return Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length, options, matchLengthPtr);
+ }
+ }
+ }
+
+ private unsafe int IndexOfOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!target.IsEmpty);
+ Debug.Assert(_isAsciiEqualityOrdinal);
+
+ fixed (char* ap = &MemoryMarshal.GetReference(source))
+ fixed (char* bp = &MemoryMarshal.GetReference(target))
+ {
+ char* a = ap;
+ char* b = bp;
+ int endIndex = source.Length - target.Length;
+
+ if (endIndex < 0)
+ goto InteropCall;
+
+ for (int j = 0; j < target.Length; j++)
+ {
+ char targetChar = *(b + j);
+ if (targetChar >= 0x80 || s_highCharTable[targetChar])
+ goto InteropCall;
+ }
+
+ int i = 0;
+ for (; i <= endIndex; i++)
+ {
+ int targetIndex = 0;
+ int sourceIndex = i;
+
+ for (; targetIndex < target.Length; targetIndex++)
+ {
+ char valueChar = *(a + sourceIndex);
+ char targetChar = *(b + targetIndex);
+
+ if (valueChar == targetChar && valueChar < 0x80 && !s_highCharTable[valueChar])
+ {
+ sourceIndex++;
+ continue;
+ }
+
+ // uppercase both chars - notice that we need just one compare per char
+ if ((uint)(valueChar - 'a') <= ('z' - 'a'))
+ valueChar = (char)(valueChar - 0x20);
+ if ((uint)(targetChar - 'a') <= ('z' - 'a'))
+ targetChar = (char)(targetChar - 0x20);
+
+ if (valueChar >= 0x80 || s_highCharTable[valueChar])
+ goto InteropCall;
+ else if (valueChar != targetChar)
+ break;
+ sourceIndex++;
+ }
+
+ if (targetIndex == target.Length)
+ {
+ if (matchLengthPtr != null)
+ *matchLengthPtr = target.Length;
+ return i;
+ }
+ }
+ if (i > endIndex)
+ return -1;
+ InteropCall:
+ return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
+ }
+ }
+
+ private unsafe int IndexOfOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!target.IsEmpty);
+ Debug.Assert(_isAsciiEqualityOrdinal);
+
+ fixed (char* ap = &MemoryMarshal.GetReference(source))
+ fixed (char* bp = &MemoryMarshal.GetReference(target))
+ {
+ char* a = ap;
+ char* b = bp;
+ int endIndex = source.Length - target.Length;
+
+ if (endIndex < 0)
+ goto InteropCall;
+
+ for (int j = 0; j < target.Length; j++)
+ {
+ char targetChar = *(b + j);
+ if (targetChar >= 0x80 || s_highCharTable[targetChar])
+ goto InteropCall;
+ }
+
+ int i = 0;
+ for (; i <= endIndex; i++)
+ {
+ int targetIndex = 0;
+ int sourceIndex = i;
+
+ for (; targetIndex < target.Length; targetIndex++)
+ {
+ char valueChar = *(a + sourceIndex);
+ char targetChar = *(b + targetIndex);
+ if (valueChar >= 0x80 || s_highCharTable[valueChar])
+ goto InteropCall;
+ else if (valueChar != targetChar)
+ break;
+ sourceIndex++;
+ }
+
+ if (targetIndex == target.Length)
+ {
+ if (matchLengthPtr != null)
+ *matchLengthPtr = target.Length;
+ return i;
+ }
+ }
+ if (i > endIndex)
+ return -1;
+ InteropCall:
+ return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
+ }
+ }
+
private unsafe int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options)
{
Debug.Assert(!_invariantMode);
@@ -307,6 +501,87 @@ namespace System.Globalization
}
}
+ private unsafe bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!prefix.IsEmpty);
+ Debug.Assert(_isAsciiEqualityOrdinal);
+ Debug.Assert(source.Length >= prefix.Length);
+
+ int length = prefix.Length;
+
+ fixed (char* ap = &MemoryMarshal.GetReference(source))
+ fixed (char* bp = &MemoryMarshal.GetReference(prefix))
+ {
+ char* a = ap;
+ char* b = bp;
+
+ while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
+ {
+ int charA = *a;
+ int charB = *b;
+
+ if (charA == charB)
+ {
+ a++; b++;
+ length--;
+ continue;
+ }
+
+ // uppercase both chars - notice that we need just one compare per char
+ if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
+ if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
+
+ if (charA != charB)
+ return false;
+
+ // Next char
+ a++; b++;
+ length--;
+ }
+
+ if (length == 0) return true;
+ return Interop.Globalization.StartsWith(_sortHandle, b, length, a, length, options);
+ }
+ }
+
+ private unsafe bool StartsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!prefix.IsEmpty);
+ Debug.Assert(_isAsciiEqualityOrdinal);
+ Debug.Assert(source.Length >= prefix.Length);
+
+ int length = prefix.Length;
+
+ fixed (char* ap = &MemoryMarshal.GetReference(source))
+ fixed (char* bp = &MemoryMarshal.GetReference(prefix))
+ {
+ char* a = ap;
+ char* b = bp;
+
+ while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
+ {
+ int charA = *a;
+ int charB = *b;
+
+ if (charA != charB)
+ return false;
+
+ // Next char
+ a++; b++;
+ length--;
+ }
+
+ if (length == 0) return true;
+ return Interop.Globalization.StartsWith(_sortHandle, b, length, a, length, options);
+ }
+ }
+
private bool EndsWith(string source, string suffix, CompareOptions options)
{
Debug.Assert(!_invariantMode);
@@ -361,30 +636,20 @@ namespace System.Globalization
private unsafe bool EndsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
{
- return StartsWithOrdinalIgnoreCaseHelper(source.Slice(source.Length - suffix.Length), suffix, options);
- }
-
- private unsafe bool EndsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
- {
- return StartsWithOrdinalHelper(source.Slice(source.Length - suffix.Length), suffix, options);
- }
-
- private unsafe bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
- {
Debug.Assert(!_invariantMode);
Debug.Assert(!source.IsEmpty);
- Debug.Assert(!prefix.IsEmpty);
+ Debug.Assert(!suffix.IsEmpty);
Debug.Assert(_isAsciiEqualityOrdinal);
- Debug.Assert(source.Length >= prefix.Length);
+ Debug.Assert(source.Length >= suffix.Length);
- int length = prefix.Length;
+ int length = suffix.Length;
fixed (char* ap = &MemoryMarshal.GetReference(source))
- fixed (char* bp = &MemoryMarshal.GetReference(prefix))
+ fixed (char* bp = &MemoryMarshal.GetReference(suffix))
{
- char* a = ap;
- char* b = bp;
+ char* a = ap + source.Length - 1;
+ char* b = bp + suffix.Length - 1;
while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
{
@@ -393,7 +658,7 @@ namespace System.Globalization
if (charA == charB)
{
- a++; b++;
+ a--; b--;
length--;
continue;
}
@@ -401,61 +666,52 @@ namespace System.Globalization
// uppercase both chars - notice that we need just one compare per char
if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
-
- //Return the (case-insensitive) difference between them.
+
if (charA != charB)
return false;
// Next char
- a++; b++;
+ a--; b--;
length--;
}
if (length == 0) return true;
- return Interop.Globalization.StartsWith(_sortHandle, b, prefix.Length - length, a, prefix.Length - length, options);
+ return Interop.Globalization.EndsWith(_sortHandle, b - length + 1, length, a - length + 1, length, options);
}
}
- private unsafe bool StartsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
+ private unsafe bool EndsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
{
Debug.Assert(!_invariantMode);
Debug.Assert(!source.IsEmpty);
- Debug.Assert(!prefix.IsEmpty);
+ Debug.Assert(!suffix.IsEmpty);
Debug.Assert(_isAsciiEqualityOrdinal);
- Debug.Assert(source.Length >= prefix.Length);
+ Debug.Assert(source.Length >= suffix.Length);
- int length = prefix.Length;
+ int length = suffix.Length;
fixed (char* ap = &MemoryMarshal.GetReference(source))
- fixed (char* bp = &MemoryMarshal.GetReference(prefix))
+ fixed (char* bp = &MemoryMarshal.GetReference(suffix))
{
- char* a = ap;
- char* b = bp;
+ char* a = ap + source.Length - 1;
+ char* b = bp + suffix.Length - 1;
while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
{
int charA = *a;
int charB = *b;
-
- if (charA == charB)
- {
- a++; b++;
- length--;
- continue;
- }
-
- //Return the (case-insensitive) difference between them.
+
if (charA != charB)
return false;
// Next char
- a++; b++;
+ a--; b--;
length--;
}
if (length == 0) return true;
- return Interop.Globalization.StartsWith(_sortHandle, b, prefix.Length - length, a, prefix.Length - length, options);
+ return Interop.Globalization.EndsWith(_sortHandle, b - length + 1, length, a - length + 1, length, options);
}
}
diff --git a/src/System.Private.CoreLib/src/System/Globalization/CompareInfo.Windows.cs b/src/System.Private.CoreLib/src/System/Globalization/CompareInfo.Windows.cs
index 3ae236c6f..c0befb263 100644
--- a/src/System.Private.CoreLib/src/System/Globalization/CompareInfo.Windows.cs
+++ b/src/System.Private.CoreLib/src/System/Globalization/CompareInfo.Windows.cs
@@ -51,6 +51,28 @@ namespace System.Globalization
return ret < 0 ? ret : ret + offset;
}
}
+
+ private static unsafe int FindStringOrdinal(
+ uint dwFindStringOrdinalFlags,
+ ReadOnlySpan<char> source,
+ ReadOnlySpan<char> value,
+ bool bIgnoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pValue = &MemoryMarshal.GetReference(value))
+ {
+ int ret = Interop.Kernel32.FindStringOrdinal(
+ dwFindStringOrdinalFlags,
+ pSource,
+ source.Length,
+ pValue,
+ value.Length,
+ bIgnoreCase ? 1 : 0);
+ return ret;
+ }
+ }
internal static int IndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
{
@@ -62,6 +84,16 @@ namespace System.Globalization
return FindStringOrdinal(FIND_FROMSTART, source, startIndex, count, value, value.Length, ignoreCase);
}
+ internal static int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ return FindStringOrdinal(FIND_FROMSTART, source, value, ignoreCase);
+ }
+
internal static int LastIndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
{
Debug.Assert(!GlobalizationMode.Invariant);
@@ -284,6 +316,18 @@ namespace System.Globalization
return -1;
}
+ internal unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(target.Length != 0);
+ Debug.Assert((options == CompareOptions.None || options == CompareOptions.IgnoreCase));
+
+ int retValue = FindString(FIND_FROMSTART | (uint)GetNativeCompareFlags(options), source, target, matchLengthPtr);
+ return retValue;
+ }
+
private unsafe int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options)
{
Debug.Assert(!_invariantMode);
diff --git a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
index c9ec1e5f4..5601bae5a 100644
--- a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
+++ b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
@@ -611,7 +611,6 @@ namespace System.Runtime.CompilerServices
// - Boolean
// - Byte, SByte
// - Char
- // - Decimal
// - Int32, UInt32
// - Int64, UInt64
// - Int16, UInt16
@@ -646,7 +645,6 @@ namespace System.Runtime.CompilerServices
(typeof(TResult) == typeof(Byte) && default(Byte) == (Byte)(object)result) ||
(typeof(TResult) == typeof(SByte) && default(SByte) == (SByte)(object)result) ||
(typeof(TResult) == typeof(Char) && default(Char) == (Char)(object)result) ||
- (typeof(TResult) == typeof(Decimal) && default(Decimal) == (Decimal)(object)result) ||
(typeof(TResult) == typeof(Int64) && default(Int64) == (Int64)(object)result) ||
(typeof(TResult) == typeof(UInt64) && default(UInt64) == (UInt64)(object)result) ||
(typeof(TResult) == typeof(Int16) && default(Int16) == (Int16)(object)result) ||
diff --git a/src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs
index e227b7fa6..f8ebc13ee 100644
--- a/src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs
+++ b/src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs
@@ -333,6 +333,10 @@ namespace System.Runtime
internal static extern unsafe object RhBoxAny(void* pData, EETypePtr pEEType);
[MethodImpl(MethodImplOptions.InternalCall)]
+ [RuntimeImport(RuntimeLibrary, "RhBoxAny")]
+ internal static extern unsafe object RhBoxAny(ref byte pData, EETypePtr pEEType);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhNewObject")]
internal static extern object RhNewObject(EETypePtr pEEType);
diff --git a/src/System.Private.CoreLib/src/System/String.Comparison.cs b/src/System.Private.CoreLib/src/System/String.Comparison.cs
index 03dffedf1..914fa6aa1 100644
--- a/src/System.Private.CoreLib/src/System/String.Comparison.cs
+++ b/src/System.Private.CoreLib/src/System/String.Comparison.cs
@@ -5,9 +5,16 @@
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using Internal.Runtime.CompilerServices;
+#if BIT64
+using nuint = System.UInt64;
+#else
+using nuint = System.UInt32;
+#endif
+
namespace System
{
public partial class String
@@ -691,19 +698,36 @@ namespace System
// TODO https://github.com/dotnet/corefx/issues/21395: Expose this publicly?
internal static int CompareOrdinal(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
{
- // TODO: This needs to be optimized / unrolled. It can't just use CompareOrdinalHelper(str, str)
- // (changed to accept spans) because its implementation is based on a string layout,
- // in a way that doesn't work when there isn't guaranteed to be a null terminator.
+ // TODO: Add a vectorized code path, similar to SequenceEqual
+ // https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/SpanHelpers.byte.cs#L900
int minLength = Math.Min(strA.Length, strB.Length);
- for (int i = 0; i < minLength; i++)
+ ref char first = ref MemoryMarshal.GetReference(strA);
+ ref char second = ref MemoryMarshal.GetReference(strB);
+
+ int i = 0;
+ if (minLength >= sizeof(nuint) / sizeof(char))
+ {
+ while (i < minLength - sizeof(nuint) / sizeof(char))
+ {
+ if (Unsafe.ReadUnaligned<nuint>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) !=
+ Unsafe.ReadUnaligned<nuint>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
+ {
+ break;
+ }
+ i += sizeof(nuint) / sizeof(char);
+ }
+ }
+ while (i < minLength)
{
- if (strA[i] != strB[i])
+ char a = Unsafe.Add(ref first, i);
+ char b = Unsafe.Add(ref second, i);
+ if (a != b)
{
- return strA[i] - strB[i];
+ return a - b;
}
+ i++;
}
-
return strA.Length - strB.Length;
}
diff --git a/src/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/System.Private.CoreLib/src/System/Threading/ThreadPool.cs
index e83cdce77..c41b37753 100644
--- a/src/System.Private.CoreLib/src/System/Threading/ThreadPool.cs
+++ b/src/System.Private.CoreLib/src/System/Threading/ThreadPool.cs
@@ -691,24 +691,20 @@ namespace System.Threading
void ExecuteWorkItem();
}
- internal sealed class QueueUserWorkItemCallback : IThreadPoolWorkItem
+ internal abstract class QueueUserWorkItemCallbackBase : IThreadPoolWorkItem
{
- private WaitCallback callback;
- private readonly ExecutionContext context;
- private readonly Object state;
-
#if DEBUG
private volatile int executed;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1821:RemoveEmptyFinalizers")]
- ~QueueUserWorkItemCallback()
+ ~QueueUserWorkItemCallbackBase()
{
Debug.Assert(
executed != 0 || Environment.HasShutdownStarted /*|| AppDomain.CurrentDomain.IsFinalizingForUnload()*/,
"A QueueUserWorkItemCallback was never called!");
}
- private void MarkExecuted()
+ protected void MarkExecuted()
{
GC.SuppressFinalize(this);
Debug.Assert(
@@ -717,28 +713,51 @@ namespace System.Threading
}
#endif
- internal QueueUserWorkItemCallback(WaitCallback waitCallback, Object stateObj, ExecutionContext ec)
- {
- callback = waitCallback;
- state = stateObj;
- context = ec;
- }
-
- void IThreadPoolWorkItem.ExecuteWorkItem()
+ public virtual void ExecuteWorkItem()
{
#if DEBUG
MarkExecuted();
#endif
+ }
+ }
+
+ internal sealed class QueueUserWorkItemCallback : QueueUserWorkItemCallbackBase
+ {
+ private WaitCallback _callback;
+ private readonly object _state;
+ private readonly ExecutionContext _context;
+
+ internal static readonly ContextCallback s_executionContextShim = state =>
+ {
+ var obj = (QueueUserWorkItemCallback)state;
+ WaitCallback c = obj._callback;
+ Debug.Assert(c != null);
+ obj._callback = null;
+ c(obj._state);
+ };
+
+ internal QueueUserWorkItemCallback(WaitCallback callback, object state, ExecutionContext context)
+ {
+ _callback = callback;
+ _state = state;
+ _context = context;
+ }
+
+ public override void ExecuteWorkItem()
+ {
+ base.ExecuteWorkItem();
try
{
- if (context == null)
+ if (_context == null)
{
- WaitCallback cb = callback;
- callback = null;
- cb(state);
+ WaitCallback c = _callback;
+ _callback = null;
+ c(_state);
}
else
- ExecutionContext.Run(context, ccb, this);
+ {
+ ExecutionContext.Run(_context, s_executionContextShim, this);
+ }
}
catch (Exception e)
{
@@ -746,57 +765,80 @@ namespace System.Threading
throw; //unreachable
}
}
-
- internal static readonly ContextCallback ccb = new ContextCallback(WaitCallback_Context);
-
- private static void WaitCallback_Context(Object state)
- {
- QueueUserWorkItemCallback obj = (QueueUserWorkItemCallback)state;
- WaitCallback wc = obj.callback;
- Debug.Assert(null != wc);
- wc(obj.state);
- }
}
- internal sealed class QueueUserWorkItemCallbackDefaultContext : IThreadPoolWorkItem
+ internal sealed class QueueUserWorkItemCallback<TState> : QueueUserWorkItemCallbackBase
{
- private WaitCallback callback;
- private readonly Object state;
+ private Action<TState> _callback;
+ private readonly TState _state;
+ private readonly ExecutionContext _context;
-#if DEBUG
- private volatile int executed;
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1821:RemoveEmptyFinalizers")]
- ~QueueUserWorkItemCallbackDefaultContext()
+ internal static readonly ContextCallback s_executionContextShim = state =>
{
- Debug.Assert(
- executed != 0 || Environment.HasShutdownStarted /*|| AppDomain.CurrentDomain.IsFinalizingForUnload()*/,
- "A QueueUserWorkItemCallbackDefaultContext was never called!");
+ var obj = (QueueUserWorkItemCallback<TState>)state;
+ Action<TState> c = obj._callback;
+ Debug.Assert(c != null);
+ obj._callback = null;
+ c(obj._state);
+ };
+
+ internal QueueUserWorkItemCallback(Action<TState> callback, TState state, ExecutionContext context)
+ {
+ _callback = callback;
+ _state = state;
+ _context = context;
}
- private void MarkExecuted()
+ public override void ExecuteWorkItem()
{
- GC.SuppressFinalize(this);
- Debug.Assert(
- 0 == Interlocked.Exchange(ref executed, 1),
- "A QueueUserWorkItemCallbackDefaultContext was called twice!");
+ base.ExecuteWorkItem();
+ try
+ {
+ if (_context == null)
+ {
+ Action<TState> c = _callback;
+ _callback = null;
+ c(_state);
+ }
+ else
+ {
+ ExecutionContext.RunInternal(_context, s_executionContextShim, this);
+ }
+ }
+ catch (Exception e)
+ {
+ RuntimeAugments.ReportUnhandledException(e);
+ throw; //unreachable
+ }
}
-#endif
+ }
+
+ internal sealed class QueueUserWorkItemCallbackDefaultContext : QueueUserWorkItemCallbackBase
+ {
+ private WaitCallback _callback;
+ private readonly object _state;
- internal QueueUserWorkItemCallbackDefaultContext(WaitCallback waitCallback, Object stateObj)
+ internal static readonly ContextCallback s_executionContextShim = state =>
{
- callback = waitCallback;
- state = stateObj;
+ var obj = (QueueUserWorkItemCallbackDefaultContext)state;
+ WaitCallback c = obj._callback;
+ Debug.Assert(c != null);
+ obj._callback = null;
+ c(obj._state);
+ };
+
+ internal QueueUserWorkItemCallbackDefaultContext(WaitCallback callback, object state)
+ {
+ _callback = callback;
+ _state = state;
}
- void IThreadPoolWorkItem.ExecuteWorkItem()
+ public override void ExecuteWorkItem()
{
-#if DEBUG
- MarkExecuted();
-#endif
+ base.ExecuteWorkItem();
try
{
- ExecutionContext.Run(ExecutionContext.Default, ccb, this);
+ ExecutionContext.Run(ExecutionContext.Default, s_executionContextShim, this);
}
catch (Exception e)
{
@@ -804,16 +846,40 @@ namespace System.Threading
throw; //unreachable
}
}
+ }
+
+ internal sealed class QueueUserWorkItemCallbackDefaultContext<TState> : QueueUserWorkItemCallbackBase
+ {
+ private Action<TState> _callback;
+ private readonly TState _state;
- internal static readonly ContextCallback ccb = new ContextCallback(WaitCallback_Context);
+ internal static readonly ContextCallback s_executionContextShim = state =>
+ {
+ var obj = (QueueUserWorkItemCallbackDefaultContext<TState>)state;
+ Action<TState> c = obj._callback;
+ Debug.Assert(c != null);
+ obj._callback = null;
+ c(obj._state);
+ };
+
+ internal QueueUserWorkItemCallbackDefaultContext(Action<TState> callback, TState state)
+ {
+ _callback = callback;
+ _state = state;
+ }
- private static void WaitCallback_Context(Object state)
+ public override void ExecuteWorkItem()
{
- QueueUserWorkItemCallbackDefaultContext obj = (QueueUserWorkItemCallbackDefaultContext)state;
- WaitCallback wc = obj.callback;
- Debug.Assert(null != wc);
- obj.callback = null;
- wc(obj.state);
+ base.ExecuteWorkItem();
+ try
+ {
+ ExecutionContext.Run(ExecutionContext.Default, s_executionContextShim, this);
+ }
+ catch (Exception e)
+ {
+ RuntimeAugments.ReportUnhandledException(e);
+ throw; //unreachable
+ }
}
}
@@ -965,12 +1031,9 @@ namespace System.Threading
}
public static bool QueueUserWorkItem(WaitCallback callBack) =>
- QueueUserWorkItem(callBack, null, preferLocal: false);
-
- public static bool QueueUserWorkItem(WaitCallback callBack, object state) =>
- QueueUserWorkItem(callBack, state, preferLocal: false);
+ QueueUserWorkItem(callBack, null);
- public static bool QueueUserWorkItem(WaitCallback callBack, object state, bool preferLocal)
+ public static bool QueueUserWorkItem(WaitCallback callBack, object state)
{
if (callBack == null)
{
@@ -983,6 +1046,24 @@ namespace System.Threading
new QueueUserWorkItemCallbackDefaultContext(callBack, state) :
(IThreadPoolWorkItem)new QueueUserWorkItemCallback(callBack, state, context);
+ ThreadPoolGlobals.workQueue.Enqueue(tpcallBack, forceGlobal: true);
+
+ return true;
+ }
+
+ public static bool QueueUserWorkItem<TState>(Action<TState> callBack, TState state, bool preferLocal)
+ {
+ if (callBack == null)
+ {
+ throw new ArgumentNullException(nameof(callBack));
+ }
+
+ ExecutionContext context = ExecutionContext.Capture();
+
+ IThreadPoolWorkItem tpcallBack = context == ExecutionContext.Default ?
+ new QueueUserWorkItemCallbackDefaultContext<TState>(callBack, state) :
+ (IThreadPoolWorkItem)new QueueUserWorkItemCallback<TState>(callBack, state, context);
+
ThreadPoolGlobals.workQueue.Enqueue(tpcallBack, forceGlobal: !preferLocal);
return true;
diff --git a/src/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/System.Private.CoreLib/src/System/ThrowHelper.cs
index 1610fa116..03c70becd 100644
--- a/src/System.Private.CoreLib/src/System/ThrowHelper.cs
+++ b/src/System.Private.CoreLib/src/System/ThrowHelper.cs
@@ -93,6 +93,10 @@ namespace System
{
throw new ArgumentException(SR.Argument_DestinationTooShort);
}
+ internal static void ThrowArgumentException_OverlapAlignmentMismatch()
+ {
+ throw new ArgumentException(SR.Argument_OverlapAlignmentMismatch);
+ }
internal static void ThrowArgumentOutOfRange_IndexException()
{
throw GetArgumentOutOfRangeException(ExceptionArgument.index,
@@ -309,6 +313,10 @@ namespace System
return "format";
case ExceptionArgument.culture:
return "culture";
+ case ExceptionArgument.comparer:
+ return "comparer";
+ case ExceptionArgument.comparable:
+ return "comparable";
default:
Debug.Fail("The enum value is not defined, please check the ExceptionArgument Enum.");
return "";
@@ -398,7 +406,9 @@ namespace System
pointer,
start,
format,
- culture
+ culture,
+ comparer,
+ comparable
}
//
diff --git a/src/System.Private.CoreLib/src/System/ValueType.cs b/src/System.Private.CoreLib/src/System/ValueType.cs
index d75dd2692..2c71bbcfe 100644
--- a/src/System.Private.CoreLib/src/System/ValueType.cs
+++ b/src/System.Private.CoreLib/src/System/ValueType.cs
@@ -11,8 +11,13 @@
**
===========================================================*/
+using System.Runtime;
+
+using Internal.Runtime.CompilerServices;
using Internal.Runtime.Augments;
+using Debug = System.Diagnostics.Debug;
+
namespace System
{
// CONTRACT with Runtime
@@ -26,6 +31,7 @@ namespace System
return this.GetType().ToString();
}
+#if PROJECTN
public override bool Equals(object obj)
{
return RuntimeAugments.Callbacks.ValueTypeEqualsUsingReflection(this, obj);
@@ -35,5 +41,162 @@ namespace System
{
return RuntimeAugments.Callbacks.ValueTypeGetHashCodeUsingReflection(this);
}
+#else
+ private const int UseFastHelper = -1;
+ private const int GetNumFields = -1;
+
+ // An override of this method will be injected by the compiler into all valuetypes that cannot be compared
+ // using a simple memory comparison.
+ // This API is a bit awkward because we want to avoid burning more than one vtable slot on this.
+ // When index == GetNumFields, this method is expected to return the number of fields of this
+ // valuetype. Otherwise, it returns the offset and type handle of the index-th field on this type.
+ internal virtual int __GetFieldHelper(int index, out EETypePtr eeType)
+ {
+ // Value types that don't override this method will use the fast path that looks at bytes, not fields.
+ Debug.Assert(index == GetNumFields);
+ eeType = default;
+ return UseFastHelper;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj == null || obj.EETypePtr != this.EETypePtr)
+ return false;
+
+ int numFields = __GetFieldHelper(GetNumFields, out _);
+
+ ref byte thisRawData = ref this.GetRawData();
+ ref byte thatRawData = ref obj.GetRawData();
+
+ if (numFields == UseFastHelper)
+ {
+ // Sanity check - if there are GC references, we should not be comparing bytes
+ Debug.Assert(!this.EETypePtr.HasPointers);
+
+ // Compare the memory
+ int valueTypeSize = (int)this.EETypePtr.ValueTypeSize;
+ for (int i = 0; i < valueTypeSize; i++)
+ {
+ if (Unsafe.Add(ref thisRawData, i) != Unsafe.Add(ref thatRawData, i))
+ return false;
+ }
+ }
+ else
+ {
+ // Foreach field, box and call the Equals method.
+ for (int i = 0; i < numFields; i++)
+ {
+ int fieldOffset = __GetFieldHelper(i, out EETypePtr fieldType);
+
+ // Fetch the value of the field on both types
+ object thisField = RuntimeImports.RhBoxAny(ref Unsafe.Add(ref thisRawData, fieldOffset), fieldType);
+ object thatField = RuntimeImports.RhBoxAny(ref Unsafe.Add(ref thatRawData, fieldOffset), fieldType);
+
+ // Compare the fields
+ if (thisField == null)
+ {
+ if (thatField != null)
+ return false;
+ }
+ else if (!thisField.Equals(thatField))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public override int GetHashCode()
+ {
+ int hashCode = this.EETypePtr.GetHashCode();
+
+ hashCode ^= GetHashCodeImpl();
+
+ return hashCode;
+ }
+
+ private int GetHashCodeImpl()
+ {
+ int numFields = __GetFieldHelper(GetNumFields, out _);
+
+ if (numFields == UseFastHelper)
+ return FastGetValueTypeHashCodeHelper(this.EETypePtr, ref this.GetRawData());
+
+ return RegularGetValueTypeHashCode(this.EETypePtr, ref this.GetRawData(), numFields);
+ }
+
+ private static int FastGetValueTypeHashCodeHelper(EETypePtr type, ref byte data)
+ {
+ // Sanity check - if there are GC references, we should not be hashing bytes
+ Debug.Assert(!type.HasPointers);
+
+ int size = (int)type.ValueTypeSize;
+ int hashCode = 0;
+
+ for (int i = 0; i < size / 4; i++)
+ {
+ hashCode ^= Unsafe.As<byte, int>(ref Unsafe.Add(ref data, i * 4));
+ }
+
+ return hashCode;
+ }
+
+ private int RegularGetValueTypeHashCode(EETypePtr type, ref byte data, int numFields)
+ {
+ int hashCode = 0;
+
+ // We only take the hashcode for the first non-null field. That's what the CLR does.
+ for (int i = 0; i < numFields; i++)
+ {
+ int fieldOffset = __GetFieldHelper(i, out EETypePtr fieldType);
+ ref byte fieldData = ref Unsafe.Add(ref data, fieldOffset);
+
+ Debug.Assert(!fieldType.IsPointer);
+
+ if (fieldType.CorElementType == RuntimeImports.RhCorElementType.ELEMENT_TYPE_R4)
+ {
+ hashCode = Unsafe.Read<float>(ref fieldData).GetHashCode();
+ }
+ else if (fieldType.CorElementType == RuntimeImports.RhCorElementType.ELEMENT_TYPE_R8)
+ {
+ hashCode = Unsafe.Read<double>(ref fieldData).GetHashCode();
+ }
+ else if (fieldType.IsPrimitive)
+ {
+ hashCode = FastGetValueTypeHashCodeHelper(fieldType, ref fieldData);
+ }
+ else if (fieldType.IsValueType)
+ {
+ // We have no option but to box since this value type could have
+ // GC pointers (we could find out if we want though), or fields of type Double/Single (we can't
+ // really find out). Double/Single have weird requirements around -0.0 and +0.0.
+ // If this boxing becomes a problem, we could build a piece of infrastructure that determines the slot
+ // of __GetFieldHelper, decodes the unboxing stub pointed to by the slot to the real target
+ // (we already have that part), and calls the entrypoint that expects a byref `this`, and use the
+ // data to decide between calling fast or regular hashcode helper.
+ var fieldValue = (ValueType)RuntimeImports.RhBox(fieldType, ref fieldData);
+ hashCode = fieldValue.GetHashCodeImpl();
+ }
+ else
+ {
+ object fieldValue = Unsafe.Read<object>(ref fieldData);
+ if (fieldValue != null)
+ {
+ hashCode = fieldValue.GetHashCode();
+ }
+ else
+ {
+ // null object reference, try next
+ continue;
+ }
+ }
+ break;
+ }
+
+ return hashCode;
+ }
+#endif
}
}
diff --git a/src/System.Private.Interop/src/Internal/Runtime/CompilerHelpers/RuntimeInteropData.ProjectN.cs b/src/System.Private.Interop/src/Internal/Runtime/CompilerHelpers/RuntimeInteropData.ProjectN.cs
index 4db7c7f13..cfc7ce822 100644
--- a/src/System.Private.Interop/src/Internal/Runtime/CompilerHelpers/RuntimeInteropData.ProjectN.cs
+++ b/src/System.Private.Interop/src/Internal/Runtime/CompilerHelpers/RuntimeInteropData.ProjectN.cs
@@ -18,7 +18,7 @@ namespace Internal.Runtime.CompilerHelpers
{
McgModuleManager.GetPInvokeDelegateData(delegateTypeHandle, out McgPInvokeDelegateData data);
IntPtr pStub = data.ForwardDelegateCreationStub;
- if (pStup == IntPtr.Zero)
+ if (pStub == IntPtr.Zero)
throw new MissingInteropDataException(SR.DelegateMarshalling_MissingInteropData, Type.GetTypeFromHandle(delegateTypeHandle));
return pStub;
}
@@ -27,7 +27,7 @@ namespace Internal.Runtime.CompilerHelpers
{
McgModuleManager.GetPInvokeDelegateData(delegateTypeHandle, out McgPInvokeDelegateData pinvokeDelegateData);
IntPtr pStub = openStaticDelegate ? pinvokeDelegateData.ReverseOpenStaticDelegateStub : pinvokeDelegateData.ReverseStub;
- if (pStup == IntPtr.Zero)
+ if (pStub == IntPtr.Zero)
throw new MissingInteropDataException(SR.DelegateMarshalling_MissingInteropData, Type.GetTypeFromHandle(delegateTypeHandle));
return pStub;
}
diff --git a/src/System.Private.Interop/src/System.Private.Interop.csproj b/src/System.Private.Interop/src/System.Private.Interop.csproj
index 03ca92928..73411b210 100644
--- a/src/System.Private.Interop/src/System.Private.Interop.csproj
+++ b/src/System.Private.Interop/src/System.Private.Interop.csproj
@@ -144,6 +144,10 @@
<Compile Condition="'$(IsProjectNLibrary)' == 'true'" Include="Internal\Runtime\CompilerHelpers\RuntimeInteropData.ProjectN.cs"/>
<Compile Condition="'$(IsProjectNLibrary)' != 'true'" Include="Internal\Runtime\CompilerHelpers\RuntimeInteropData.CoreRT.cs"/>
+
+ <!-- TODO This should be really only needed for CoreCLR flavor of S.P.Interop. -->
+ <!-- See https://github.com/dotnet/corert/pull/5404 -->
+ <Compile Condition="'$(IsProjectNLibrary)' == 'true'" Include="System\Runtime\InteropServices\MarshalAdapter.cs" />
</ItemGroup>
<ItemGroup Condition="'$(IsProjectNLibrary)' != 'true'">
diff --git a/src/System.Private.Reflection.Core/src/System/Reflection/Runtime/General/TypeUnifier.cs b/src/System.Private.Reflection.Core/src/System/Reflection/Runtime/General/TypeUnifier.cs
index 98d338020..aa3f0dc82 100644
--- a/src/System.Private.Reflection.Core/src/System/Reflection/Runtime/General/TypeUnifier.cs
+++ b/src/System.Private.Reflection.Core/src/System/Reflection/Runtime/General/TypeUnifier.cs
@@ -210,6 +210,14 @@ namespace System.Reflection.Runtime.TypeInfos
if (elementTypeHandle.IsNull())
return default(RuntimeTypeHandle);
+ // The check is here on purpose - one of the implementations of IsByRefLike contains a custom attribute
+ // search and those are very expensive from size on disk footprint perspective. We purposefully
+ // place this call in a path that won't be part of the executable image unless more advanced reflection services
+ // are also needed ("pay for play"). We really don't want a typeof() to push the app into requiring the full reflection
+ // stack to be compiled into the final executable.
+ if (elementType.IsByRefLike)
+ throw new TypeLoadException(SR.Format(SR.ArgumentException_InvalidArrayElementType, elementType));
+
RuntimeTypeHandle typeHandle;
if (!multiDim)
{
@@ -272,7 +280,7 @@ namespace System.Reflection.Runtime.TypeInfos
{
Debug.Assert(multiDim || rank == 1);
- if (elementType.IsByRef || elementType.IsByRefLike)
+ if (elementType.IsByRef)
throw new TypeLoadException(SR.Format(SR.ArgumentException_InvalidArrayElementType, elementType));
// We only permit creating parameterized types if the pay-for-play policy specifically allows them *or* if the result
diff --git a/src/dirs.proj b/src/dirs.proj
index 081e77994..bc9ce5cad 100644
--- a/src/dirs.proj
+++ b/src/dirs.proj
@@ -6,11 +6,6 @@
<ExcludeProjects Condition="'$(OSEnvironment)'!='Windows_NT'" Include="**\TypeSystem.Tests.csproj" />
<ExcludeProjects Condition="'$(OSEnvironment)'!='Windows_NT'" Include="**\ILCompiler.MetadataTransform.Tests.csproj" />
- <!-- These projects target netcoreapp2.0, so they are disabled until #3682 is done -->
- <ExcludeProjects Include="**\ILVerification.csproj" />
- <ExcludeProjects Include="**\ILVerification.Tests.csproj" />
- <ExcludeProjects Include="**\ILVerify.csproj" />
-
<Project Include="AotPackageReference\AotPackageReference.depproj" />
<Project Include="Framework\Framework.depproj" />