diff options
author | Michal Strehovský <MichalStrehovsky@users.noreply.github.com> | 2018-03-02 18:23:00 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-03-02 18:23:00 +0300 |
commit | 87e4f1151a4be22ac6cbecf373a5dd31e42537f8 (patch) | |
tree | 118528f30dcb3d9d20e54c3ed2403700676f7b7f /src | |
parent | edb197215a19131df0fb4e2a42b542bafa5a1664 (diff) | |
parent | e462dd690354275402ae6b6835d9009639c08dc6 (diff) |
Merge pull request #5483 from MichalStrehovsky/nmirror-merge
Merge master to nmirror
Diffstat (limited to 'src')
141 files changed, 6558 insertions, 2479 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/Common/src/TypeSystem/Interop/IL/Marshaller.cs b/src/Common/src/TypeSystem/Interop/IL/Marshaller.cs index 46fdf63e8..ef75539f1 100644 --- a/src/Common/src/TypeSystem/Interop/IL/Marshaller.cs +++ b/src/Common/src/TypeSystem/Interop/IL/Marshaller.cs @@ -1831,7 +1831,7 @@ namespace Internal.TypeSystem.Interop LoadManagedValue(codeStream); codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken( - Context.GetHelperEntryPoint("InteropHelpers", "GetStubForPInvokeDelegate"))); + Context.GetHelperEntryPoint("InteropHelpers", "GetFunctionPointerForDelegate"))); StoreNativeValue(codeStream); } @@ -1842,7 +1842,7 @@ namespace Internal.TypeSystem.Interop codeStream.Emit(ILOpcode.ldtoken, _ilCodeStreams.Emitter.NewToken(ManagedType)); codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken( - Context.GetHelperEntryPoint("InteropHelpers", "GetPInvokeDelegateForStub"))); + Context.GetHelperEntryPoint("InteropHelpers", "GetDelegateForFunctionPointer"))); StoreManagedValue(codeStream); } diff --git a/src/ILCompiler.Compiler/src/Compiler/CompilerTypeSystemContext.cs b/src/ILCompiler.Compiler/src/Compiler/CompilerTypeSystemContext.cs index a75fc506d..4feb7a582 100644 --- a/src/ILCompiler.Compiler/src/Compiler/CompilerTypeSystemContext.cs +++ b/src/ILCompiler.Compiler/src/Compiler/CompilerTypeSystemContext.cs @@ -352,6 +352,10 @@ namespace ILCompiler { return GetAllMethodsForEnum(type); } + else if (type.IsValueType) + { + return GetAllMethodsForValueType(type); + } return type.GetMethods(); } 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/ILCompiler.WebAssembly/src/ILCompiler.WebAssembly.csproj b/src/ILCompiler.WebAssembly/src/ILCompiler.WebAssembly.csproj index 2eb7e3eeb..58b4c14eb 100644 --- a/src/ILCompiler.WebAssembly/src/ILCompiler.WebAssembly.csproj +++ b/src/ILCompiler.WebAssembly/src/ILCompiler.WebAssembly.csproj @@ -18,10 +18,6 @@ </ItemGroup> <ItemGroup Condition="'$(IsProjectNLibrary)' != 'true'"> - <ProjectReference Include="libLLVMdep.depproj"> - <ReferenceOutputAssembly>false</ReferenceOutputAssembly> - <SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties> - </ProjectReference> <ProjectReference Include="..\..\ILCompiler.DependencyAnalysisFramework\src\ILCompiler.DependencyAnalysisFramework.csproj" /> <ProjectReference Include="..\..\ILCompiler.MetadataTransform\src\ILCompiler.MetadataTransform.csproj" /> <ProjectReference Include="..\..\ILCompiler.MetadataWriter\src\ILCompiler.MetadataWriter.csproj" /> @@ -55,17 +51,18 @@ <Compile Include="CodeGen\NodeDataSection.cs" /> <Compile Include="CodeGen\ILToWebAssemblyImporter.cs" /> </ItemGroup> + <ItemGroup> <Content Include="..\..\..\packages\llvmsharp\5.0.0\lib\netstandard1.1\LLVMSharp.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> - <Content Include="..\..\..\packages\libllvm\4.0.0\runtimes\osx\native\libLLVM.dylib" Condition="'$(HostOS)' == 'OSX'"> + <Content Include="..\..\..\packages\libllvm\4.0.0\runtimes\osx\native\libLLVM.dylib" Condition="'$(NuPkgRid)' == 'osx-x64'"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> - <Content Include="..\..\..\packages\libllvm\4.0.0\runtimes\linux-x64\native\libLLVM.so" Condition="'$(HostOS)' == 'Linux'"> + <Content Include="..\..\..\packages\libllvm\4.0.0\runtimes\linux-x64\native\libLLVM.so" Condition="'$(NuPkgRid)' == 'linux-x64'"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> - <Content Include="..\..\..\packages\libllvm\4.0.0\runtimes\win-x64\native\libLLVM.dll" Condition="'$(HostOS)' == 'Windows_NT'"> + <Content Include="..\..\..\packages\libllvm\4.0.0\runtimes\win-x64\native\libLLVM.dll" Condition="'$(NuPkgRid)' == 'win-x64' or '$(NuPkgRid)' == ''"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> </ItemGroup> diff --git a/src/ILCompiler/ILCompiler.sln b/src/ILCompiler/ILCompiler.sln index 428f3f74d..b8f37a7b3 100644 --- a/src/ILCompiler/ILCompiler.sln +++ b/src/ILCompiler/ILCompiler.sln @@ -38,11 +38,15 @@ Global Debug|Any CPU = Debug|Any CPU Debug|arm = Debug|arm Debug|arm64 = Debug|arm64 + Debug|armel = Debug|armel + Debug|wasm = Debug|wasm Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|arm = Release|arm Release|arm64 = Release|arm64 + Release|armel = Release|armel + Release|wasm = Release|wasm Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection @@ -52,6 +56,10 @@ Global {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Debug|arm.Build.0 = Debug|arm {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Debug|arm64.ActiveCfg = Debug|arm64 {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Debug|arm64.Build.0 = Debug|arm64 + {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Debug|armel.ActiveCfg = Debug|Any CPU + {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Debug|armel.Build.0 = Debug|Any CPU + {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Debug|wasm.ActiveCfg = Debug|Any CPU + {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Debug|wasm.Build.0 = Debug|Any CPU {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Debug|x64.ActiveCfg = Debug|x64 {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Debug|x64.Build.0 = Debug|x64 {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Debug|x86.ActiveCfg = Debug|x86 @@ -62,6 +70,10 @@ Global {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Release|arm.Build.0 = Release|arm {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Release|arm64.ActiveCfg = Release|arm64 {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Release|arm64.Build.0 = Release|arm64 + {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Release|armel.ActiveCfg = Release|Any CPU + {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Release|armel.Build.0 = Release|Any CPU + {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Release|wasm.ActiveCfg = Release|Any CPU + {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Release|wasm.Build.0 = Release|Any CPU {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Release|x64.ActiveCfg = Release|x64 {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Release|x64.Build.0 = Release|x64 {DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}.Release|x86.ActiveCfg = Release|x86 @@ -71,6 +83,10 @@ Global {FBA029C3-B184-4457-AEEC-38D0C2523067}.Debug|arm.Build.0 = Debug|arm {FBA029C3-B184-4457-AEEC-38D0C2523067}.Debug|arm64.ActiveCfg = Debug|arm64 {FBA029C3-B184-4457-AEEC-38D0C2523067}.Debug|arm64.Build.0 = Debug|arm64 + {FBA029C3-B184-4457-AEEC-38D0C2523067}.Debug|armel.ActiveCfg = Debug|Any CPU + {FBA029C3-B184-4457-AEEC-38D0C2523067}.Debug|armel.Build.0 = Debug|Any CPU + {FBA029C3-B184-4457-AEEC-38D0C2523067}.Debug|wasm.ActiveCfg = Debug|Any CPU + {FBA029C3-B184-4457-AEEC-38D0C2523067}.Debug|wasm.Build.0 = Debug|Any CPU {FBA029C3-B184-4457-AEEC-38D0C2523067}.Debug|x64.ActiveCfg = Debug|x64 {FBA029C3-B184-4457-AEEC-38D0C2523067}.Debug|x64.Build.0 = Debug|x64 {FBA029C3-B184-4457-AEEC-38D0C2523067}.Debug|x86.ActiveCfg = Debug|x86 @@ -80,6 +96,10 @@ Global {FBA029C3-B184-4457-AEEC-38D0C2523067}.Release|arm.Build.0 = Release|arm {FBA029C3-B184-4457-AEEC-38D0C2523067}.Release|arm64.ActiveCfg = Release|arm64 {FBA029C3-B184-4457-AEEC-38D0C2523067}.Release|arm64.Build.0 = Release|arm64 + {FBA029C3-B184-4457-AEEC-38D0C2523067}.Release|armel.ActiveCfg = Release|Any CPU + {FBA029C3-B184-4457-AEEC-38D0C2523067}.Release|armel.Build.0 = Release|Any CPU + {FBA029C3-B184-4457-AEEC-38D0C2523067}.Release|wasm.ActiveCfg = Release|Any CPU + {FBA029C3-B184-4457-AEEC-38D0C2523067}.Release|wasm.Build.0 = Release|Any CPU {FBA029C3-B184-4457-AEEC-38D0C2523067}.Release|x64.ActiveCfg = Release|x64 {FBA029C3-B184-4457-AEEC-38D0C2523067}.Release|x64.Build.0 = Release|x64 {FBA029C3-B184-4457-AEEC-38D0C2523067}.Release|x86.ActiveCfg = Release|x86 @@ -89,6 +109,10 @@ Global {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Debug|arm.Build.0 = Debug|arm {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Debug|arm64.ActiveCfg = Debug|arm64 {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Debug|arm64.Build.0 = Debug|arm64 + {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Debug|armel.ActiveCfg = Debug|Any CPU + {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Debug|armel.Build.0 = Debug|Any CPU + {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Debug|wasm.ActiveCfg = Debug|Any CPU + {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Debug|wasm.Build.0 = Debug|Any CPU {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Debug|x64.ActiveCfg = Debug|x64 {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Debug|x64.Build.0 = Debug|x64 {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Debug|x86.ActiveCfg = Debug|x86 @@ -98,6 +122,10 @@ Global {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Release|arm.Build.0 = Release|arm {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Release|arm64.ActiveCfg = Release|arm64 {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Release|arm64.Build.0 = Release|arm64 + {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Release|armel.ActiveCfg = Release|Any CPU + {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Release|armel.Build.0 = Release|Any CPU + {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Release|wasm.ActiveCfg = Release|Any CPU + {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Release|wasm.Build.0 = Release|Any CPU {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Release|x64.ActiveCfg = Release|x64 {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Release|x64.Build.0 = Release|x64 {1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}.Release|x86.ActiveCfg = Release|x86 @@ -107,6 +135,10 @@ Global {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|arm.Build.0 = Debug|arm {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|arm64.ActiveCfg = Debug|arm64 {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|arm64.Build.0 = Debug|arm64 + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|armel.ActiveCfg = Debug|Any CPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|armel.Build.0 = Debug|Any CPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|wasm.ActiveCfg = Debug|Any CPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|wasm.Build.0 = Debug|Any CPU {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|x64.ActiveCfg = Debug|x64 {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|x64.Build.0 = Debug|x64 {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|x86.ActiveCfg = Debug|x86 @@ -116,6 +148,10 @@ Global {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|arm.Build.0 = Release|arm {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|arm64.ActiveCfg = Release|arm64 {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|arm64.Build.0 = Release|arm64 + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|armel.ActiveCfg = Release|Any CPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|armel.Build.0 = Release|Any CPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|wasm.ActiveCfg = Release|Any CPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|wasm.Build.0 = Release|Any CPU {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|x64.ActiveCfg = Release|x64 {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|x64.Build.0 = Release|x64 {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|x86.ActiveCfg = Release|x86 @@ -125,6 +161,10 @@ Global {13BB3788-C3EB-4046-8105-A95F8AE49404}.Debug|arm.Build.0 = Debug|arm {13BB3788-C3EB-4046-8105-A95F8AE49404}.Debug|arm64.ActiveCfg = Debug|arm64 {13BB3788-C3EB-4046-8105-A95F8AE49404}.Debug|arm64.Build.0 = Debug|arm64 + {13BB3788-C3EB-4046-8105-A95F8AE49404}.Debug|armel.ActiveCfg = Debug|Any CPU + {13BB3788-C3EB-4046-8105-A95F8AE49404}.Debug|armel.Build.0 = Debug|Any CPU + {13BB3788-C3EB-4046-8105-A95F8AE49404}.Debug|wasm.ActiveCfg = Debug|Any CPU + {13BB3788-C3EB-4046-8105-A95F8AE49404}.Debug|wasm.Build.0 = Debug|Any CPU {13BB3788-C3EB-4046-8105-A95F8AE49404}.Debug|x64.ActiveCfg = Debug|x64 {13BB3788-C3EB-4046-8105-A95F8AE49404}.Debug|x64.Build.0 = Debug|x64 {13BB3788-C3EB-4046-8105-A95F8AE49404}.Debug|x86.ActiveCfg = Debug|x86 @@ -134,6 +174,10 @@ Global {13BB3788-C3EB-4046-8105-A95F8AE49404}.Release|arm.Build.0 = Release|arm {13BB3788-C3EB-4046-8105-A95F8AE49404}.Release|arm64.ActiveCfg = Release|arm64 {13BB3788-C3EB-4046-8105-A95F8AE49404}.Release|arm64.Build.0 = Release|arm64 + {13BB3788-C3EB-4046-8105-A95F8AE49404}.Release|armel.ActiveCfg = Release|Any CPU + {13BB3788-C3EB-4046-8105-A95F8AE49404}.Release|armel.Build.0 = Release|Any CPU + {13BB3788-C3EB-4046-8105-A95F8AE49404}.Release|wasm.ActiveCfg = Release|Any CPU + {13BB3788-C3EB-4046-8105-A95F8AE49404}.Release|wasm.Build.0 = Release|Any CPU {13BB3788-C3EB-4046-8105-A95F8AE49404}.Release|x64.ActiveCfg = Release|x64 {13BB3788-C3EB-4046-8105-A95F8AE49404}.Release|x64.Build.0 = Release|x64 {13BB3788-C3EB-4046-8105-A95F8AE49404}.Release|x86.ActiveCfg = Release|x86 @@ -143,6 +187,10 @@ Global {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Debug|arm.Build.0 = Debug|arm {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Debug|arm64.ActiveCfg = Debug|arm64 {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Debug|arm64.Build.0 = Debug|arm64 + {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Debug|armel.ActiveCfg = Debug|armel + {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Debug|armel.Build.0 = Debug|armel + {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Debug|wasm.ActiveCfg = Debug|wasm + {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Debug|wasm.Build.0 = Debug|wasm {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Debug|x64.ActiveCfg = Debug|x64 {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Debug|x86.ActiveCfg = Debug|x86 {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Debug|x86.Build.0 = Debug|x86 @@ -151,6 +199,10 @@ Global {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Release|arm.Build.0 = Release|arm {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Release|arm64.ActiveCfg = Release|arm64 {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Release|arm64.Build.0 = Release|arm64 + {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Release|armel.ActiveCfg = Release|armel + {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Release|armel.Build.0 = Release|armel + {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Release|wasm.ActiveCfg = Release|wasm + {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Release|wasm.Build.0 = Release|wasm {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Release|x64.ActiveCfg = Release|x64 {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Release|x64.Build.0 = Release|x64 {BE95C560-B508-4588-8907-F9FC5BC1A0CF}.Release|x86.ActiveCfg = Release|x86 @@ -161,6 +213,10 @@ Global {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Debug|arm.Build.0 = Debug|Any CPU {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Debug|arm64.ActiveCfg = Debug|Any CPU {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Debug|arm64.Build.0 = Debug|Any CPU + {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Debug|armel.ActiveCfg = Debug|Any CPU + {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Debug|armel.Build.0 = Debug|Any CPU + {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Debug|wasm.ActiveCfg = Debug|Any CPU + {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Debug|wasm.Build.0 = Debug|Any CPU {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Debug|x64.ActiveCfg = Debug|Any CPU {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Debug|x64.Build.0 = Debug|Any CPU {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -171,6 +227,10 @@ Global {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Release|arm.Build.0 = Release|Any CPU {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Release|arm64.ActiveCfg = Release|Any CPU {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Release|arm64.Build.0 = Release|Any CPU + {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Release|armel.ActiveCfg = Release|Any CPU + {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Release|armel.Build.0 = Release|Any CPU + {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Release|wasm.ActiveCfg = Release|Any CPU + {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Release|wasm.Build.0 = Release|Any CPU {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Release|x64.ActiveCfg = Release|Any CPU {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Release|x64.Build.0 = Release|Any CPU {D66338D4-F9E4-4051-B302-232C6BFB6EF6}.Release|x86.ActiveCfg = Release|Any CPU @@ -181,6 +241,10 @@ Global {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Debug|arm.Build.0 = Debug|Any CPU {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Debug|arm64.ActiveCfg = Debug|Any CPU {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Debug|arm64.Build.0 = Debug|Any CPU + {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Debug|armel.ActiveCfg = Debug|Any CPU + {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Debug|armel.Build.0 = Debug|Any CPU + {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Debug|wasm.ActiveCfg = Debug|Any CPU + {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Debug|wasm.Build.0 = Debug|Any CPU {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Debug|x64.ActiveCfg = Debug|Any CPU {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Debug|x64.Build.0 = Debug|Any CPU {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -191,6 +255,10 @@ Global {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Release|arm.Build.0 = Release|Any CPU {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Release|arm64.ActiveCfg = Release|Any CPU {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Release|arm64.Build.0 = Release|Any CPU + {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Release|armel.ActiveCfg = Release|Any CPU + {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Release|armel.Build.0 = Release|Any CPU + {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Release|wasm.ActiveCfg = Release|Any CPU + {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Release|wasm.Build.0 = Release|Any CPU {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Release|x64.ActiveCfg = Release|Any CPU {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Release|x64.Build.0 = Release|Any CPU {A965EA82-219D-48F7-AD51-BC030C16CC6F}.Release|x86.ActiveCfg = Release|Any CPU @@ -201,6 +269,10 @@ Global {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Debug|arm.Build.0 = Debug|Any CPU {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Debug|arm64.ActiveCfg = Debug|Any CPU {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Debug|arm64.Build.0 = Debug|Any CPU + {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Debug|armel.ActiveCfg = Debug|Any CPU + {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Debug|armel.Build.0 = Debug|Any CPU + {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Debug|wasm.ActiveCfg = Debug|Any CPU + {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Debug|wasm.Build.0 = Debug|Any CPU {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Debug|x64.ActiveCfg = Debug|Any CPU {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Debug|x64.Build.0 = Debug|Any CPU {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -211,6 +283,10 @@ Global {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Release|arm.Build.0 = Release|Any CPU {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Release|arm64.ActiveCfg = Release|Any CPU {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Release|arm64.Build.0 = Release|Any CPU + {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Release|armel.ActiveCfg = Release|Any CPU + {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Release|armel.Build.0 = Release|Any CPU + {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Release|wasm.ActiveCfg = Release|Any CPU + {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Release|wasm.Build.0 = Release|Any CPU {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Release|x64.ActiveCfg = Release|Any CPU {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Release|x64.Build.0 = Release|Any CPU {971AE7E7-08C6-48B4-902A-63851E6DAC66}.Release|x86.ActiveCfg = Release|Any CPU @@ -221,6 +297,10 @@ Global {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Debug|arm.Build.0 = Debug|arm {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Debug|arm64.ActiveCfg = Debug|arm64 {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Debug|arm64.Build.0 = Debug|arm64 + {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Debug|armel.ActiveCfg = Debug|Any CPU + {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Debug|armel.Build.0 = Debug|Any CPU + {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Debug|wasm.ActiveCfg = Debug|Any CPU + {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Debug|wasm.Build.0 = Debug|Any CPU {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Debug|x64.ActiveCfg = Debug|x64 {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Debug|x64.Build.0 = Debug|x64 {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Debug|x86.ActiveCfg = Debug|x86 @@ -231,6 +311,10 @@ Global {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Release|arm.Build.0 = Release|arm {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Release|arm64.ActiveCfg = Release|arm64 {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Release|arm64.Build.0 = Release|arm64 + {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Release|armel.ActiveCfg = Release|Any CPU + {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Release|armel.Build.0 = Release|Any CPU + {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Release|wasm.ActiveCfg = Release|Any CPU + {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Release|wasm.Build.0 = Release|Any CPU {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Release|x64.ActiveCfg = Release|x64 {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Release|x64.Build.0 = Release|x64 {5D796936-CCC1-4E9D-B1A9-36A5ABA9B499}.Release|x86.ActiveCfg = Release|x86 @@ -241,6 +325,10 @@ Global {CE9781B1-0028-4039-A48C-8193F0D28467}.Debug|arm.Build.0 = Debug|Any CPU {CE9781B1-0028-4039-A48C-8193F0D28467}.Debug|arm64.ActiveCfg = Debug|Any CPU {CE9781B1-0028-4039-A48C-8193F0D28467}.Debug|arm64.Build.0 = Debug|Any CPU + {CE9781B1-0028-4039-A48C-8193F0D28467}.Debug|armel.ActiveCfg = Debug|Any CPU + {CE9781B1-0028-4039-A48C-8193F0D28467}.Debug|armel.Build.0 = Debug|Any CPU + {CE9781B1-0028-4039-A48C-8193F0D28467}.Debug|wasm.ActiveCfg = Debug|Any CPU + {CE9781B1-0028-4039-A48C-8193F0D28467}.Debug|wasm.Build.0 = Debug|Any CPU {CE9781B1-0028-4039-A48C-8193F0D28467}.Debug|x64.ActiveCfg = Debug|x64 {CE9781B1-0028-4039-A48C-8193F0D28467}.Debug|x64.Build.0 = Debug|x64 {CE9781B1-0028-4039-A48C-8193F0D28467}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -251,6 +339,10 @@ Global {CE9781B1-0028-4039-A48C-8193F0D28467}.Release|arm.Build.0 = Release|Any CPU {CE9781B1-0028-4039-A48C-8193F0D28467}.Release|arm64.ActiveCfg = Release|Any CPU {CE9781B1-0028-4039-A48C-8193F0D28467}.Release|arm64.Build.0 = Release|Any CPU + {CE9781B1-0028-4039-A48C-8193F0D28467}.Release|armel.ActiveCfg = Release|Any CPU + {CE9781B1-0028-4039-A48C-8193F0D28467}.Release|armel.Build.0 = Release|Any CPU + {CE9781B1-0028-4039-A48C-8193F0D28467}.Release|wasm.ActiveCfg = Release|Any CPU + {CE9781B1-0028-4039-A48C-8193F0D28467}.Release|wasm.Build.0 = Release|Any CPU {CE9781B1-0028-4039-A48C-8193F0D28467}.Release|x64.ActiveCfg = Release|x64 {CE9781B1-0028-4039-A48C-8193F0D28467}.Release|x64.Build.0 = Release|x64 {CE9781B1-0028-4039-A48C-8193F0D28467}.Release|x86.ActiveCfg = Release|Any CPU @@ -261,6 +353,10 @@ Global {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Debug|arm.Build.0 = Debug|arm {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Debug|arm64.ActiveCfg = Debug|arm64 {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Debug|arm64.Build.0 = Debug|arm64 + {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Debug|armel.ActiveCfg = Debug|Any CPU + {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Debug|armel.Build.0 = Debug|Any CPU + {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Debug|wasm.ActiveCfg = Debug|Any CPU + {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Debug|wasm.Build.0 = Debug|Any CPU {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Debug|x64.ActiveCfg = Debug|x64 {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Debug|x64.Build.0 = Debug|x64 {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Debug|x86.ActiveCfg = Debug|x86 @@ -271,6 +367,10 @@ Global {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Release|arm.Build.0 = Release|arm {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Release|arm64.ActiveCfg = Release|arm64 {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Release|arm64.Build.0 = Release|arm64 + {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Release|armel.ActiveCfg = Release|Any CPU + {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Release|armel.Build.0 = Release|Any CPU + {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Release|wasm.ActiveCfg = Release|Any CPU + {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Release|wasm.Build.0 = Release|Any CPU {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Release|x64.ActiveCfg = Release|x64 {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Release|x64.Build.0 = Release|x64 {44DE7F7B-AD73-40EA-87CC-554F2F57C38A}.Release|x86.ActiveCfg = Release|x86 diff --git a/src/ILVerification/StrongNameKeys/ILVerify.snk b/src/ILVerification/StrongNameKeys/ILVerify.snk Binary files differnew file mode 100644 index 000000000..e5534f1a9 --- /dev/null +++ b/src/ILVerification/StrongNameKeys/ILVerify.snk diff --git a/src/ILVerification/src/AssemblyInfo.cs b/src/ILVerification/src/AssemblyInfo.cs index 722ae4299..cd0ba8baa 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=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] +[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..9341ef395 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("FxResources.ILVerification.SR", typeof(Verifier).GetTypeInfo().Assembly)); private ILVerifyTypeSystemContext _typeSystemContext; diff --git a/src/ILVerification/tests/ILMethodTester.cs b/src/ILVerification/tests/ILMethodTester.cs index c3f35f19c..eb92c3dcb 100644 --- a/src/ILVerification/tests/ILMethodTester.cs +++ b/src/ILVerification/tests/ILMethodTester.cs @@ -6,10 +6,11 @@ using System.Collections.Generic; using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; +using ILVerify; using Internal.TypeSystem.Ecma; using Xunit; -namespace ILVerify.Tests +namespace ILVerification.Tests { public class ILMethodTester { @@ -48,7 +49,7 @@ namespace ILVerify.Tests foreach (var item in invalidIL.ExpectedVerifierErrors) { var actual = results.Select(e => e.ToString()); - Assert.True(results.Where(r => r.Error.Code == item).Count() > 0, $"Actual errors where: {string.Join(',', actual)}"); + Assert.True(results.Where(r => r.Error.Code == item).Count() > 0, $"Actual errors where: {string.Join(",", actual)}"); } } } diff --git a/src/ILVerification/tests/ILVerification.Tests.csproj b/src/ILVerification/tests/ILVerification.Tests.csproj index ceca7fc93..4d5f17abf 100644 --- a/src/ILVerification/tests/ILVerification.Tests.csproj +++ b/src/ILVerification/tests/ILVerification.Tests.csproj @@ -1,15 +1,23 @@ -<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> + <TargetFramework>netstandard1.5</TargetFramework> <IsPackable>false</IsPackable> </PropertyGroup> <ItemGroup> + <Compile Include="ILMethodTester.cs" /> + <Compile Include="TestDataLoader.cs" /> + </ItemGroup> + + <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170427-09" /> <PackageReference Include="xunit" Version="2.2.0" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" /> + <PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> </ItemGroup> <ItemGroup> @@ -24,4 +32,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/ILVerification/tests/TestDataLoader.cs b/src/ILVerification/tests/TestDataLoader.cs index ed22d1b1d..91a1195f8 100644 --- a/src/ILVerification/tests/TestDataLoader.cs +++ b/src/ILVerification/tests/TestDataLoader.cs @@ -10,12 +10,13 @@ using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Text; +using ILVerify; using Internal.TypeSystem.Ecma; using Newtonsoft.Json; using Xunit; using Xunit.Abstractions; -namespace ILVerify.Tests +namespace ILVerification.Tests { /// <summary> /// Parses the methods in the test assemblies. @@ -169,10 +170,10 @@ namespace ILVerify.Tests simpleNameToPathMap.Add(Path.GetFileNameWithoutExtension(fileName), TESTASSEMBLYPATH + fileName); } - Assembly coreAssembly = typeof(object).Assembly; + Assembly coreAssembly = typeof(object).GetTypeInfo().Assembly; simpleNameToPathMap.Add(coreAssembly.GetName().Name, coreAssembly.Location); - Assembly systemRuntime = Assembly.Load("System.Runtime"); + Assembly systemRuntime = Assembly.Load(new AssemblyName("System.Runtime")); simpleNameToPathMap.Add(systemRuntime.GetName().Name, systemRuntime.Location); var resolver = new TestResolver(simpleNameToPathMap); 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/MiscHelpers.cpp b/src/Native/Runtime/MiscHelpers.cpp index fb553a0fb..c74571587 100644 --- a/src/Native/Runtime/MiscHelpers.cpp +++ b/src/Native/Runtime/MiscHelpers.cpp @@ -707,7 +707,7 @@ COOP_PINVOKE_HELPER(Boolean, RhpArrayCopy, (Array * pSourceArray, Int32 sourceIn // // This function handles all cases of Array.Clear that do not require conversions. It returns false if the operation cannot be performed, leaving -// the handling of the complex cases or throwing apppropriate exception to the higher level framework. It is only allowed to return false for illegal +// the handling of the complex cases or throwing appropriate exception to the higher level framework. It is only allowed to return false for illegal // calls as the BCL side has fallback for "complex cases" only. // COOP_PINVOKE_HELPER(Boolean, RhpArrayClear, (Array * pArray, Int32 index, Int32 length)) 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/RuntimeInstance.cpp b/src/Native/Runtime/RuntimeInstance.cpp index 7270d13ba..0f1db5c64 100644 --- a/src/Native/Runtime/RuntimeInstance.cpp +++ b/src/Native/Runtime/RuntimeInstance.cpp @@ -918,7 +918,7 @@ COOP_PINVOKE_HELPER(void *, RhNewInterfaceDispatchCell, (EEType * pInterface, In return NULL; // Due to the synchronization mechanism used to update this indirection cell we must ensure the cell's alignment is twice that of a pointer. - // Fortunately, Windows heap guarantees this aligment. + // Fortunately, Windows heap guarantees this alignment. ASSERT(IS_ALIGNED(pCell, 2 * POINTER_SIZE)); ASSERT(IS_ALIGNED(pInterface, (InterfaceDispatchCell::IDC_CachePointerMask + 1))); 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/stressLog.cpp b/src/Native/Runtime/stressLog.cpp index 5e6278091..a0b097c89 100644 --- a/src/Native/Runtime/stressLog.cpp +++ b/src/Native/Runtime/stressLog.cpp @@ -71,7 +71,7 @@ unsigned __int64 getTimeStamp() { #endif // _X86_ else /*********************************************************************************/ -/* Get the the frequency cooresponding to 'getTimeStamp'. For non-x86 +/* Get the the frequency corresponding to 'getTimeStamp'. For non-x86 architectures, this is just the performance counter frequency. */ unsigned __int64 getTickFrequency() 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 173e1da41..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) @@ -3620,7 +3620,7 @@ gc_heap* seg_mapping_table_heap_of_worker (uint8_t* o) gc_heap* hp = ((o > entry->boundary) ? entry->h1 : entry->h0); - dprintf (2, ("checking obj %Ix, index is %Id, entry: boundry: %Ix, h0: %Ix, seg0: %Ix, h1: %Ix, seg1: %Ix", + dprintf (2, ("checking obj %Ix, index is %Id, entry: boundary: %Ix, h0: %Ix, seg0: %Ix, h1: %Ix, seg1: %Ix", o, index, (entry->boundary + 1), (uint8_t*)(entry->h0), (uint8_t*)(entry->seg0), (uint8_t*)(entry->h1), (uint8_t*)(entry->seg1))); @@ -3689,7 +3689,7 @@ heap_segment* seg_mapping_table_segment_of (uint8_t* o) size_t index = (size_t)o / gc_heap::min_segment_size; seg_mapping* entry = &seg_mapping_table[index]; - dprintf (2, ("checking obj %Ix, index is %Id, entry: boundry: %Ix, seg0: %Ix, seg1: %Ix", + dprintf (2, ("checking obj %Ix, index is %Id, entry: boundary: %Ix, seg0: %Ix, seg1: %Ix", o, index, (entry->boundary + 1), (uint8_t*)(entry->seg0), (uint8_t*)(entry->seg1))); @@ -3701,7 +3701,7 @@ heap_segment* seg_mapping_table_segment_of (uint8_t* o) if (seg) { - // Can't assert this when it's callled by everyone (it's true when it's called by mark cards). + // Can't assert this when it's called by everyone (it's true when it's called by mark cards). //assert (in_range_for_segment (o, seg)); if (in_range_for_segment (o, seg)) { @@ -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) @@ -4893,7 +4893,7 @@ extern "C" uint64_t __rdtsc(); #elif defined(_TARGET_WASM_) static ptrdiff_t get_cycle_count() { - // @WASMTODO: cycle counter is not exposed to user mode by WebAsssembly. For now (until we can show this + // @WASMTODO: cycle counter is not exposed to user mode by WebAssembly. For now (until we can show this // makes a difference on the configurations on which we'll run) just return 0. This will result in // all buffer access times being reported as equal in access_time(). return 0; @@ -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 @@ -8281,7 +8281,7 @@ void gc_heap::combine_mark_lists() assert (end_of_list < &g_mark_list [n_heaps*mark_list_size]); if (end_of_list > &g_mark_list[0]) _sort (&g_mark_list[0], end_of_list, 0); - //adjust the mark_list to the begining of the resulting mark list. + //adjust the mark_list to the beginning of the resulting mark list. for (int i = 0; i < n_heaps; i++) { g_heaps [i]->mark_list = g_mark_list; @@ -8292,7 +8292,7 @@ void gc_heap::combine_mark_lists() else { uint8_t** end_of_list = g_mark_list; - //adjust the mark_list to the begining of the resulting mark list. + //adjust the mark_list to the beginning of the resulting mark list. //put the index beyond the end to turn off mark list processing for (int i = 0; i < n_heaps; i++) { @@ -8403,7 +8403,7 @@ class seg_free_spaces struct free_space_bucket { seg_free_space* free_space; - ptrdiff_t count_add; // Assigned when we first contruct the array. + ptrdiff_t count_add; // Assigned when we first construct the array. ptrdiff_t count_fit; // How many items left when we are fitting plugs. }; @@ -8691,7 +8691,7 @@ public: // BARTOKTODO (4841): this code path is disabled (see can_fit_all_blocks_p) until we take alignment requirements into account _ASSERTE(requiredAlignment == DATA_ALIGNMENT && false); #endif // FEATURE_STRUCTALIGN - // TODO: this is also not large alignment ready. We would need to consider alignment when chosing the + // TODO: this is also not large alignment ready. We would need to consider alignment when choosing the // the bucket. size_t plug_size_to_fit = plug_size; @@ -10441,7 +10441,7 @@ gc_heap::init_gc_heap (int h_number) { #ifndef INTERIOR_POINTERS //set the brick_table for large objects - //but default value is clearded + //but default value is cleared //clear_brick_table ((uint8_t*)heap_segment_mem (lseg), // (uint8_t*)heap_segment_reserved (lseg)); @@ -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; @@ -21072,7 +21072,7 @@ void gc_heap::compact_loh() { if (!heap_segment_read_only_p (seg)) { - // We grew the segment to accommondate allocations. + // We grew the segment to accommodate allocations. if (heap_segment_plan_allocated (seg) > heap_segment_allocated (seg)) { if ((heap_segment_plan_allocated (seg) - plug_skew) > heap_segment_used (seg)) @@ -21292,7 +21292,7 @@ void gc_heap::convert_to_pinned_plug (BOOL& last_npinned_plug_p, artificial_pinned_size = ps; } -// Because we have the artifical pinning, we can't gaurantee that pinned and npinned +// Because we have the artificial pinning, we can't guarantee that pinned and npinned // plugs are always interleaved. void gc_heap::store_plug_gap_info (uint8_t* plug_start, uint8_t* plug_end, @@ -24443,7 +24443,7 @@ void gc_heap::copy_cards_range (uint8_t* dest, uint8_t* src, size_t len, BOOL co clear_card_for_addresses (dest, dest + len); } -// POPO TODO: We should actually just recover the artifically made gaps here..because when we copy +// POPO TODO: We should actually just recover the artificially made gaps here..because when we copy // we always copy the earlier plugs first which means we won't need the gap sizes anymore. This way // we won't need to individually recover each overwritten part of plugs. inline @@ -25430,7 +25430,7 @@ BOOL gc_heap::commit_mark_array_bgc_init (uint32_t* mark_array_addr) // the mark_array flag for these segments will remain the same. BOOL gc_heap::commit_new_mark_array (uint32_t* new_mark_array_addr) { - dprintf (GC_TABLE_LOG, ("commiting existing segs on MA %Ix", new_mark_array_addr)); + dprintf (GC_TABLE_LOG, ("committing existing segs on MA %Ix", new_mark_array_addr)); generation* gen = generation_of (max_generation); heap_segment* seg = heap_segment_in_range (generation_start_segment (gen)); while (1) @@ -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 @@ -34092,7 +34092,7 @@ BOOL GCHeap::StressHeap(gc_alloc_context * context) if (str) { // Chop off the end of the string and form a new object out of it. - // This will 'free' an object at the begining of the heap, which will + // This will 'free' an object at the beginning of the heap, which will // force data movement. Note that we can only do this so many times. // before we have to move on to the next string. unsigned sizeOfNewObj = (unsigned)Align(min_obj_size * 31); @@ -34924,7 +34924,7 @@ void gc_heap::record_interesting_info_per_heap() heap_number, (size_t)settings.gc_index, settings.condemned_generation, - // TEMP - I am just doing this for wks GC 'cuase I wanna see the pattern of doing C/S GCs. + // TEMP - I am just doing this for wks GC 'cause I wanna see the pattern of doing C/S GCs. (settings.compaction ? (((compact_reason >= 0) && gc_heap_compact_reason_mandatory_p[compact_reason]) ? "M" : "W") : ""), // compaction ((expand_mechanism >= 0)? "X" : ""), // EX ((expand_mechanism == expand_reuse_normal) ? "X" : ""), // NF @@ -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++) { @@ -35466,10 +35466,10 @@ int GCHeap::GetLOHCompactionMode() return pGenGCHeap->loh_compaction_mode; } -void GCHeap::SetLOHCompactionMode (int newLOHCompactionyMode) +void GCHeap::SetLOHCompactionMode (int newLOHCompactionMode) { #ifdef FEATURE_LOH_COMPACTION - pGenGCHeap->loh_compaction_mode = (gc_loh_compaction_mode)newLOHCompactionyMode; + pGenGCHeap->loh_compaction_mode = (gc_loh_compaction_mode)newLOHCompactionMode; #endif //FEATURE_LOH_COMPACTION } @@ -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 86ce62d5b..504630b9a 100644 --- a/src/Native/gc/handletablescan.cpp +++ b/src/Native/gc/handletablescan.cpp @@ -46,7 +46,7 @@ MaskDWORD is also non-zero. 2. AgeEphemeral. When Ephemeral GC happens, ages for handles which belong to the GC condemned generation should be incremented by 1. The operation is done by calculating a new uint32_t using the old uint32_t value: NewGenerationDWORD = COMPUTE_AGED_CLUMPS(OldGenerationDWORD, BuildAgeMask(condemnedGeneration, MaxGen)) -so that if a byte in OldGenerationDWORD is smaller than or equals to condemnedGeneration. the coresponding byte in +so that if a byte in OldGenerationDWORD is smaller than or equals to condemnedGeneration. the corresponding byte in NewGenerationDWORD is 1 bigger than the old value, otherwise it remains unchanged. 3. Age. Similar as AgeEphemeral, but we use a special mask if condemned generation is max gen (2): @@ -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 @@ -1630,7 +1630,7 @@ void SegmentScanByTypeChain(PTR_TableSegment pSegment, uint32_t uType, BLOCKSCAN } while ((uNext == uLast) && (uNext != uHead)); - // call the calback for this group of blocks + // call the callback for this group of blocks pfnBlockHandler(pSegment, uBlock, (uLast - uBlock), pInfo); // advance to the next block @@ -1694,7 +1694,7 @@ void SegmentScanByTypeMap(PTR_TableSegment pSegment, const BOOL *rgTypeInclusion break; } - // call the calback for the group of blocks we found + // call the callback for the group of blocks we found pfnBlockHandler(pSegment, uFirst, (uBlock - uFirst), pInfo); // look for another range starting with the next block diff --git a/src/Native/gc/objecthandle.cpp b/src/Native/gc/objecthandle.cpp index e8eed9300..28dcc5222 100644 --- a/src/Native/gc/objecthandle.cpp +++ b/src/Native/gc/objecthandle.cpp @@ -84,7 +84,7 @@ void CALLBACK PromoteRefCounted(_UNCHECKED_OBJECTREF *pObjRef, uintptr_t *pExtra WRAPPER_NO_CONTRACT; UNREFERENCED_PARAMETER(pExtraInfo); - // there are too many races when asychnronously scanning ref-counted handles so we no longer support it + // there are too many races when asynchronously scanning ref-counted handles so we no longer support it _ASSERTE(!((ScanContext*)lp1)->concurrent); LOG((LF_GC, LL_INFO1000, LOG_HANDLE_OBJECT_CLASS("", pObjRef, "causes promotion of ", *pObjRef))); @@ -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 0b4ab0ed1..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> @@ -69,7 +69,7 @@ static const int tccMilliSecondsToMicroSeconds = 1000; // The number of nanoseconds in a millisecond. static const int tccMilliSecondsToNanoSeconds = 1000000; -// The cachced number of logical CPUs observed. +// The cached number of logical CPUs observed. static uint32_t g_logicalCpuCount = 0; // Helper memory page used by the FlushProcessWriteBuffers 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..06030450c 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,17 @@ 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); +#if PROJECTN + internal static extern unsafe uint GetFullPathNameW(string path, uint numBufferChars, char* buffer, IntPtr mustBeZero); + + // Works around https://devdiv.visualstudio.com/web/wi.aspx?pcguid=011b8bdf-6d56-4f87-be0d-0092136884d9&id=575202 + internal static unsafe uint GetFullPathNameW(string path, uint numBufferChars, ref char buffer, IntPtr mustBeZero) + { + fixed (char* pBuffer = &buffer) + return GetFullPathNameW(path, numBufferChars, pBuffer, mustBeZero); + } +#else + internal static extern uint GetFullPathNameW(string path, uint numBufferChars, ref char buffer, IntPtr mustBeZero); +#endif } } 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..09e98d0c9 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,17 @@ 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); +#if PROJECTN + internal static extern unsafe uint GetLongPathNameW(ref char lpszShortPath, char* lpszLongPath, uint cchBuffer); + + // Works around https://devdiv.visualstudio.com/web/wi.aspx?pcguid=011b8bdf-6d56-4f87-be0d-0092136884d9&id=575202 + internal static unsafe uint GetLongPathNameW(ref char lpszShortPath, ref char lpszLongPath, uint cchBuffer) + { + fixed (char* plpszLongPath = &lpszLongPath) + return GetLongPathNameW(ref lpszShortPath, plpszLongPath, cchBuffer); + } +#else + internal static extern uint GetLongPathNameW(ref char lpszShortPath, ref char lpszLongPath, uint cchBuffer); +#endif } } 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 7e7fc2895..b86412112 100644 --- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems @@ -149,6 +149,7 @@ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\DaylightTime.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\DigitShapes.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\EastAsianLunisolarCalendar.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GlobalizationExtensions.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GregorianCalendar.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GregorianCalendarHelper.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GregorianCalendarTypes.cs" /> @@ -240,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" /> @@ -444,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" /> @@ -485,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" /> @@ -716,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 1bc8ee67f..72da4a9e1 100644 --- a/src/System.Private.CoreLib/shared/System/Collections/Generic/ValueListBuilder.cs +++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/ValueListBuilder.cs @@ -23,6 +23,15 @@ namespace System.Collections.Generic public int Length => _pos; + public ref T this[int index] + { + get + { + Debug.Assert(index < _pos); + return ref _span[index]; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Append(T item) { @@ -34,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/GlobalizationExtensions.cs b/src/System.Private.CoreLib/shared/System/Globalization/GlobalizationExtensions.cs new file mode 100644 index 000000000..007283aa6 --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/Globalization/GlobalizationExtensions.cs @@ -0,0 +1,32 @@ +// 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; +using System.Diagnostics; + +namespace System.Globalization +{ + public static class GlobalizationExtensions + { + public static StringComparer GetStringComparer(this CompareInfo compareInfo, CompareOptions options) + { + if (compareInfo == null) + { + throw new ArgumentNullException(nameof(compareInfo)); + } + + if (options == CompareOptions.Ordinal) + { + return StringComparer.Ordinal; + } + + if (options == CompareOptions.OrdinalIgnoreCase) + { + return StringComparer.OrdinalIgnoreCase; + } + + return new CultureAwareComparer(compareInfo, options); + } + } +} 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/Globalization/TextInfo.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Unix.cs index 5186394df..d13d3e8ce 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Unix.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Unix.cs @@ -81,24 +81,26 @@ namespace System.Globalization { if (IsAsciiCasingSameAsInvariant) { - int length = source.Length; + int length = 0; char* a = pSource, b = pResult; if (toUpper) { - while (length-- != 0 && *a < 0x80) + while (length < source.Length && *a < 0x80) { *b++ = ToUpperAsciiInvariant(*a++); + length++; } } else { - while (length-- != 0 && *a < 0x80) + while (length < source.Length && *a < 0x80) { *b++ = ToLowerAsciiInvariant(*a++); + length++; } } - if (length != 0) + if (length != source.Length) { ChangeCase(a, source.Length - length, b, destination.Length - length, toUpper); } 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 d104219df..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) @@ -116,20 +116,19 @@ namespace System.IO return path.Length > 0 && path[0] == PathInternal.DirectorySeparatorChar; } - // The resulting string is null if path is null. If the path is empty or - // only contains whitespace characters an ArgumentException gets thrown. + /// <summary> + /// Returns the path root or null if path is empty or null. + /// </summary> public static string GetPathRoot(string path) { - if (path == null) return null; - if (PathInternal.IsEffectivelyEmpty(path)) - throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); + if (PathInternal.IsEffectivelyEmpty(path)) return null; - return IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString : String.Empty; + return IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString : string.Empty; } 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 eff8dead3..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,13 +148,14 @@ 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) { int length = path.Length; - return (length >= 1 && PathInternal.IsDirectorySeparator(path[0])) || (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar); + return (length >= 1 && PathInternal.IsDirectorySeparator(path[0])) + || (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar); } // Returns the root portion of the given path. The resulting string @@ -196,13 +169,10 @@ namespace System.IO // only contains whitespace characters an ArgumentException gets thrown. public static string GetPathRoot(string path) { - if (path == null) - return null; - if (PathInternal.IsEffectivelyEmpty(path)) - throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); + return null; - ReadOnlySpan<char> result = GetPathRoot(path.AsReadOnlySpan()); + ReadOnlySpan<char> result = GetPathRoot(path.AsSpan()); if (path.Length == result.Length) return PathInternal.NormalizeDirectorySeparators(path); @@ -224,7 +194,6 @@ namespace System.IO /// <summary>Gets whether the system is case-sensitive.</summary> internal static bool IsCaseSensitive { get { return false; } } - /// <summary> /// Returns the volume name for dos, UNC and device paths. /// </summary> @@ -251,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 eeb9abcde..41ae1cd0b 100644 --- a/src/System.Private.CoreLib/shared/System/IO/Path.cs +++ b/src/System.Private.CoreLib/shared/System/IO/Path.cs @@ -3,7 +3,6 @@ // 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; @@ -52,7 +51,7 @@ namespace System.IO s = path.Substring(0, i); break; } - if (PathInternal.IsDirectoryOrVolumeSeparator(ch)) break; + if (PathInternal.IsDirectorySeparator(ch)) break; } if (extension != null && path.Length != 0) @@ -67,29 +66,30 @@ namespace System.IO return null; } - // Returns the directory path of a file path. This method effectively - // removes the last element of the given file path, i.e. it returns a - // string consisting of all characters up to but not including the last - // backslash ("\") in the file path. The returned value is null if the file - // path is null or if the file path denotes a root (such as "\", "C:", or - // "\\server\share"). + /// <summary> + /// Returns the directory portion of a file path. This method effectively + /// removes the last segment of the given file path, i.e. it returns a + /// string consisting of all characters up to but not including the last + /// backslash ("\") in the file path. The returned value is null if the + /// specified path is null, empty, or a root (such as "\", "C:", or + /// "\\server\share"). + /// </summary> + /// <remarks> + /// Directory separators are normalized in the returned string. + /// </remarks> public static string GetDirectoryName(string path) { - if (path == null) + if (path == null || PathInternal.IsEffectivelyEmpty(path)) return null; - if (PathInternal.IsEffectivelyEmpty(path)) - throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); - - path = PathInternal.NormalizeDirectorySeparators(path); - int end = PathInternal.GetDirectoryNameOffset(path); - return end >= 0 ? path.Substring(0, end) : null; + int end = GetDirectoryNameOffset(path); + return end >= 0 ? PathInternal.NormalizeDirectorySeparators(path.Substring(0, end)) : null; } /// <summary> - /// Returns the directory path of a file path. - /// The returned value the an empty ReadOnlySpan if path is empty or - /// if the file path denotes as root (such as "\", "C:", or "\\server\share"). + /// Returns the directory portion of a file path. The returned value is empty + /// if the specified path is null, empty, or a root (such as "\", "C:", or + /// "\\server\share"). /// </summary> /// <remarks> /// Unlike the string overload, this method will not normalize directory separators. @@ -99,27 +99,45 @@ namespace System.IO if (PathInternal.IsEffectivelyEmpty(path)) return ReadOnlySpan<char>.Empty; - int end = PathInternal.GetDirectoryNameOffset(path); + int end = GetDirectoryNameOffset(path); return end >= 0 ? path.Slice(0, end) : ReadOnlySpan<char>.Empty; } - // 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. + private static int GetDirectoryNameOffset(ReadOnlySpan<char> path) + { + int rootLength = PathInternal.GetRootLength(path); + int end = path.Length; + if (end <= rootLength) + return -1; + + while (end > rootLength && !PathInternal.IsDirectorySeparator(path[--end])); + + // Trim off any remaining separators (to deal with C:\foo\\bar) + while (end > rootLength && PathInternal.IsDirectorySeparator(path[end - 1])) + end--; + + return end; + } + + /// <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> /// Returns the extension of the given path. /// </summary> /// <remarks> - /// The returned value is an empty ReadOnlySpan if the given path doesnot include an extension. + /// The returned value is an empty ReadOnlySpan if the given path does not include an extension. /// </remarks> public static ReadOnlySpan<char> GetExtension(ReadOnlySpan<char> path) { @@ -135,35 +153,46 @@ namespace System.IO else return ReadOnlySpan<char>.Empty; } - if (PathInternal.IsDirectoryOrVolumeSeparator(ch)) + if (PathInternal.IsDirectorySeparator(ch)) break; } 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; return new string(result); } - /// <summary> + /// <summary> /// The returned ReadOnlySpan contains the characters of the path that follows the last separator in path. /// </summary> public static ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path) { - int offset = PathInternal.FindFileNameIndex(path); - int count = path.Length - offset; - return path.Slice(offset, count); + int root = GetPathRoot(path).Length; + + // We don't want to cut off "C:\file.txt:stream" (i.e. should be "file.txt:stream") + // but we *do* want "C:Foo" => "Foo". This necessitates checking for the root. + + for (int i = path.Length; --i >= 0;) + { + if (i < root || PathInternal.IsDirectorySeparator(path[i])) + return path.Slice(i + 1, path.Length - i - 1); + } + + return path; } public static string GetFileNameWithoutExtension(string path) @@ -171,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; @@ -183,17 +212,17 @@ namespace System.IO /// </summary> public static ReadOnlySpan<char> GetFileNameWithoutExtension(ReadOnlySpan<char> path) { - int length = path.Length; - int offset = PathInternal.FindFileNameIndex(path); - - int end = path.Slice(offset, length - offset).LastIndexOf('.'); - return end == -1 ? - path.Slice(offset) : // No extension was found - path.Slice(offset, end); + ReadOnlySpan<char> fileName = GetFileName(path); + int lastPeriod = fileName.LastIndexOf('.'); + return lastPeriod == -1 ? + fileName : // No extension was found + 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]; @@ -223,10 +252,9 @@ namespace System.IO public static bool IsPathFullyQualified(string path) { if (path == null) - { throw new ArgumentNullException(nameof(path)); - } - return IsPathFullyQualified(path.AsReadOnlySpan()); + + return IsPathFullyQualified(path.AsSpan()); } public static bool IsPathFullyQualified(ReadOnlySpan<char> path) @@ -234,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; } @@ -256,7 +284,7 @@ namespace System.IO { return i != path.Length - 1; } - if (PathInternal.IsDirectoryOrVolumeSeparator(ch)) + if (PathInternal.IsDirectorySeparator(ch)) break; } return false; @@ -267,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) @@ -275,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) @@ -283,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) @@ -322,7 +350,7 @@ namespace System.IO } char ch = paths[i][paths[i].Length - 1]; - if (!PathInternal.IsDirectoryOrVolumeSeparator(ch)) + if (!PathInternal.IsDirectorySeparator(ch)) finalSize++; } @@ -342,7 +370,7 @@ namespace System.IO else { char ch = finalPath[finalPath.Length - 1]; - if (!PathInternal.IsDirectoryOrVolumeSeparator(ch)) + if (!PathInternal.IsDirectorySeparator(ch)) { finalPath.Append(PathInternal.DirectorySeparatorChar); } @@ -354,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 @@ -371,100 +490,62 @@ 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; + if (string.IsNullOrEmpty(first)) + return second; if (string.IsNullOrEmpty(second)) return first; - if (IsPathRooted(second.AsReadOnlySpan())) + if (IsPathRooted(second.AsSpan())) return second; - return CombineNoChecksInternal(first, second); + return JoinInternal(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); - } - - 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); - - return CombineNoChecksInternal(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); + if (IsPathRooted(second.AsSpan())) + return CombineInternal(second, third); - return CombineNoChecksInternal(first, second, third, fourth); + return JoinInternal(first, second, third); } - 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); - if (string.IsNullOrEmpty(third)) - return CombineNoChecks(first, second, fourth); - if (string.IsNullOrEmpty(fourth)) - return CombineNoChecks(first, second, third); + return CombineInternal(first, third, fourth); + if (string.IsNullOrEmpty(third)) + return CombineInternal(first, second, fourth); + if (string.IsNullOrEmpty(fourth)) + 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"); @@ -486,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"); @@ -514,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"); @@ -617,7 +698,7 @@ namespace System.IO // Remove "//", "/./", and "/../" from the path by copying each character to the output, // except the ones we're removing, such that the builder contains the normalized path // at the end. - var sb = StringBuilderCache.Acquire(path.Length); + StringBuilder sb = StringBuilderCache.Acquire(path.Length); if (skip > 0) { sb.Append(path, 0, skip); @@ -779,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 a0dba661f..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,197 +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())); - - segmentLength = fullPath.Length - lastSeparator - 1; + Span<char> initialBuffer = stackalloc char[PathInternal.MaxShortPath]; + ValueStringBuilder builder = new ValueStringBuilder(initialBuffer); - if (foundTilde && segmentLength <= MaxShortName) - possibleShortPath = true; + // Get the full path + GetFullPathName(path, ref builder); - // 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(); - } - } + // 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(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsDosUnc(ref StringBuffer buffer) - { - return !PathInternal.IsDevice(ref buffer) && buffer.Length > 1 && buffer[0] == '\\' && buffer[1] == '\\'; + // Clear the buffer + builder.Dispose(); + return result; } - 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)); - // Historically we would skip leading spaces *only* if the path started with a drive " C:" or a UNC " \\" - int startIndex = PathInternal.PathStartSkip(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 + startIndex, (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; @@ -212,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: // @@ -250,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.Unix.cs b/src/System.Private.CoreLib/shared/System/IO/PathInternal.Unix.cs index 186882a0c..0e31731a6 100644 --- a/src/System.Private.CoreLib/shared/System/IO/PathInternal.Unix.cs +++ b/src/System.Private.CoreLib/shared/System/IO/PathInternal.Unix.cs @@ -77,19 +77,6 @@ namespace System.IO return builder.ToString(); } - /// <summary> - /// Returns true if the character is a directory or volume separator. - /// </summary> - /// <param name="ch">The character to test.</param> - internal static bool IsDirectoryOrVolumeSeparator(char ch) - { - // The directory separator, volume separator, and the alternate directory - // separator should be the same on Unix, so we only need to check one. - Debug.Assert(DirectorySeparatorChar == AltDirectorySeparatorChar); - Debug.Assert(DirectorySeparatorChar == VolumeSeparatorChar); - return ch == DirectorySeparatorChar; - } - internal static bool IsPartiallyQualified(ReadOnlySpan<char> path) { // This is much simpler than Windows where paths can be rooted, but not fully qualified (such as Drive Relative) 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 98a81f1fa..2ef666959 100644 --- a/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text; namespace System.IO @@ -132,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,49 +188,56 @@ namespace System.IO { int pathLength = path.Length; int i = 0; - int volumeSeparatorLength = 2; // Length to the colon "C:" - int uncRootLength = 2; // Length to the start of the server name "\\" - bool extendedSyntax = StartsWithOrdinal(path, ExtendedPathPrefix); - bool extendedUncSyntax = StartsWithOrdinal(path, UncExtendedPathPrefix); - if (extendedSyntax) + bool deviceSyntax = IsDevice(path); + bool deviceUnc = deviceSyntax && IsDeviceUNC(path); + + if ((!deviceSyntax || deviceUnc) && pathLength > 0 && IsDirectorySeparator(path[0])) { - // Shift the position we look for the root from to account for the extended prefix - if (extendedUncSyntax) + // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo") + if (deviceUnc || (pathLength > 1 && IsDirectorySeparator(path[1]))) { - // "\\" -> "\\?\UNC\" - uncRootLength = UncExtendedPathPrefix.Length; + // UNC (\\?\UNC\ or \\), scan past server\share + + // Start past the prefix ("\\" or "\\?\UNC\") + i = deviceUnc ? UncExtendedPrefixLength : UncPrefixLength; + + // Skip two separators at most + int n = 2; + while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0)) + i++; } else { - // "C:" -> "\\?\C:" - volumeSeparatorLength += ExtendedPathPrefix.Length; + // Current drive rooted (e.g. "\foo") + i = 1; } } - - if ((!extendedSyntax || extendedUncSyntax) && pathLength > 0 && IsDirectorySeparator(path[0])) + else if (deviceSyntax) { - // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo") + // Device path (e.g. "\\?\.", "\\.\") + // Skip any characters following the prefix that aren't a separator + i = DevicePrefixLength; + while (i < pathLength && !IsDirectorySeparator(path[i])) + i++; - i = 1; // Drive rooted (\foo) is one character - if (extendedUncSyntax || (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\) - i = uncRootLength; - int n = 2; // Maximum separators to skip - while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0)) - i++; - } + // If there is another separator take it, as long as we have had at least one + // non-separator after the prefix (e.g. don't take "\\?\\", but take "\\?\a\") + if (i < pathLength && i > DevicePrefixLength && IsDirectorySeparator(path[i])) + i++; } - else if (pathLength >= volumeSeparatorLength && path[volumeSeparatorLength - 1] == VolumeSeparatorChar) + else if (pathLength >= 2 + && path[1] == VolumeSeparatorChar + && IsValidDriveChar(path[0])) { - // Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:) - // If the colon is followed by a directory separator, move past it - i = volumeSeparatorLength; - if (pathLength >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) + // Valid drive specified path ("C:", "D:", etc.) + i = 2; + + // If the colon is followed by a directory separator, move past it (e.g "C:\") + if (pathLength > 2 && IsDirectorySeparator(path[2])) i++; } + return i; } @@ -275,29 +293,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)] @@ -344,32 +339,29 @@ namespace System.IO return path; char current; - int start = PathStartSkip(path); - if (start == 0) - { - // Make a pass to see if we need to normalize so we can potentially skip allocating - bool normalized = true; + // Make a pass to see if we need to normalize so we can potentially skip allocating + bool normalized = true; - for (int i = 0; i < path.Length; i++) + for (int i = 0; i < path.Length; i++) + { + current = path[i]; + if (IsDirectorySeparator(current) + && (current != DirectorySeparatorChar + // Check for sequential separators past the first position (we need to keep initial two for UNC/extended) + || (i > 0 && i + 1 < path.Length && IsDirectorySeparator(path[i + 1])))) { - current = path[i]; - if (IsDirectorySeparator(current) - && (current != DirectorySeparatorChar - // Check for sequential separators past the first position (we need to keep initial two for UNC/extended) - || (i > 0 && i + 1 < path.Length && IsDirectorySeparator(path[i + 1])))) - { - normalized = false; - break; - } + normalized = false; + break; } - - if (normalized) - return path; } + if (normalized) + return path; + StringBuilder builder = new StringBuilder(path.Length); + int start = 0; if (IsDirectorySeparator(path[start])) { start++; @@ -400,15 +392,6 @@ namespace System.IO } /// <summary> - /// Returns true if the character is a directory or volume separator. - /// </summary> - /// <param name="ch">The character to test.</param> - internal static bool IsDirectoryOrVolumeSeparator(char ch) - { - return IsDirectorySeparator(ch) || VolumeSeparatorChar == ch; - } - - /// <summary> /// Returns true if the path is effectively empty for the current OS. /// For unix, this is empty or null. For Windows, this is empty, null, or /// just spaces ((char)32). diff --git a/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs b/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs index 3b6bcaadd..f2f350ddd 100644 --- a/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs +++ b/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs @@ -2,43 +2,20 @@ // 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.Text; - namespace System.IO { /// <summary>Contains internal path helpers that are shared between many projects.</summary> internal static partial class PathInternal { /// <summary> - /// Returns the start index of the filename - /// in the given path, or 0 if no directory - /// or volume separator is found. + /// Returns true if the path ends in a directory separator. /// </summary> - /// <param name="path">The path in which to find the index of the filename.</param> - /// <remarks> - /// This method returns path.Length for - /// inputs like "/usr/foo/" on Unix. As such, - /// it is not safe for being used to index - /// the string without additional verification. - /// </remarks> - internal static int FindFileNameIndex(ReadOnlySpan<char> path) - { - for (int i = path.Length - 1; i >= 0; i--) - { - char ch = path[i]; - if (IsDirectoryOrVolumeSeparator(ch)) - return i + 1; - } - - return 0; // the whole path is the filename - } + internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) => path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]); /// <summary> - /// Returns true if the path ends in a directory separator. + /// Returns true if the path starts in a directory separator. /// </summary> - internal static bool EndsInDirectorySeparator(string path) => - !string.IsNullOrEmpty(path) && IsDirectorySeparator(path[path.Length - 1]); + 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. @@ -112,37 +89,5 @@ namespace System.IO length: firstRootLength, comparisonType: comparisonType) == 0; } - - /// <summary> - /// Returns false for ".." unless it is specified as a part of a valid File/Directory name. - /// (Used to avoid moving up directories.) - /// - /// Valid: a..b abc..d - /// Invalid: ..ab ab.. .. abc..d\abc.. - /// </summary> - internal static void CheckSearchPattern(string searchPattern) - { - int index; - while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1) - { - // Terminal ".." . Files names cannot end in ".." - if (index + 2 == searchPattern.Length - || IsDirectorySeparator(searchPattern[index + 2])) - throw new ArgumentException(SR.Format(SR.Arg_InvalidSearchPattern, searchPattern)); - - searchPattern = searchPattern.Substring(index + 2); - } - } - - internal static int GetDirectoryNameOffset(ReadOnlySpan<char> path) - { - int root = GetRootLength(path); - int i = path.Length; - if (i <= root) - return -1; - - while (i > root && !IsDirectorySeparator(path[--i])); - return i; - } } } 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 (<0 or >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 (<0 or >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/Numerics/Vector.tt b/src/System.Private.CoreLib/shared/System/Numerics/Vector.tt index 82aa73d22..275f47350 100644 --- a/src/System.Private.CoreLib/shared/System/Numerics/Vector.tt +++ b/src/System.Private.CoreLib/shared/System/Numerics/Vector.tt @@ -68,12 +68,12 @@ namespace System.Numerics /// Returns a vector containing all zeroes. /// </summary> public static Vector<T> Zero - { + { [Intrinsic] get { return s_zero; - } + } } private static readonly Vector<T> s_zero = new Vector<T>(); @@ -84,7 +84,7 @@ namespace System.Numerics { [Intrinsic] get - { + { return s_one; } } @@ -999,7 +999,7 @@ namespace System.Numerics [MethodImplAttribute(MethodImplOptions.AggressiveInlining)] public static Vector<T> operator ~(Vector<T> value) { - return allOnes ^ value; + return s_allOnes ^ value; } #endregion Bitwise Operators diff --git a/src/System.Private.CoreLib/shared/System/ReadOnlySpan.Fast.cs b/src/System.Private.CoreLib/shared/System/ReadOnlySpan.Fast.cs index 2a30b2a92..9bf3f211a 100644 --- a/src/System.Private.CoreLib/shared/System/ReadOnlySpan.Fast.cs +++ b/src/System.Private.CoreLib/shared/System/ReadOnlySpan.Fast.cs @@ -23,7 +23,7 @@ namespace System /// or native memory, or to memory allocated on the stack. It is type- and memory-safe. /// </summary> [DebuggerTypeProxy(typeof(SpanDebugView<>))] - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{ToString(),raw}")] [NonVersionable] public readonly ref partial struct ReadOnlySpan<T> { diff --git a/src/System.Private.CoreLib/shared/System/ReadOnlySpan.cs b/src/System.Private.CoreLib/shared/System/ReadOnlySpan.cs index 4d747605a..906a3c431 100644 --- a/src/System.Private.CoreLib/shared/System/ReadOnlySpan.cs +++ b/src/System.Private.CoreLib/shared/System/ReadOnlySpan.cs @@ -18,7 +18,7 @@ namespace System /// or native memory, or to memory allocated on the stack. It is type- and memory-safe. /// </summary> [DebuggerTypeProxy(typeof(SpanDebugView<>))] - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{ToString(),raw}")] public readonly ref partial struct ReadOnlySpan<T> { /// <summary> 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 cc815d059..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,11 +35,58 @@ 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; } /// <summary> + /// Gets an <see cref="OwnedMemory{T}"/> from the underlying readOnlyMemory. + /// If unable to get the <typeparamref name="TOwner"/> type, returns false. + /// </summary> + /// <typeparam name="T">The element type of the <paramref name="readOnlyMemory" />.</typeparam> + /// <typeparam name="TOwner">The type of <see cref="OwnedMemory{T}"/> to try and retrive.</typeparam> + /// <param name="readOnlyMemory">The memory to get the owner for.</param> + /// <param name="ownedMemory">The returned owner of the <see cref="ReadOnlyMemory{T}"/>.</param> + /// <returns>A <see cref="bool"/> indicating if it was successful.</returns> + public static bool TryGetOwnedMemory<T, TOwner>(ReadOnlyMemory<T> readOnlyMemory, out TOwner ownedMemory) + where TOwner : OwnedMemory<T> + { + TOwner owner; // Use register for null comparison rather than byref + ownedMemory = owner = readOnlyMemory.GetObjectStartLength(out int index, out int length) as TOwner; + return !ReferenceEquals(owner, null); + } + + /// <summary> + /// Gets an <see cref="OwnedMemory{T}"/> and <paramref name="index" />, <paramref name="length" /> from the underlying memory. + /// If unable to get the <typeparamref name="TOwner"/> type, returns false. + /// </summary> + /// <typeparam name="T">The element type of the <paramref name="readOnlyMemory" />.</typeparam> + /// <typeparam name="TOwner">The type of <see cref="OwnedMemory{T}"/> to try and retrive.</typeparam> + /// <param name="readOnlyMemory">The memory to get the owner for.</param> + /// <param name="ownedMemory">The returned owner of the <see cref="ReadOnlyMemory{T}"/>.</param> + /// <param name="index">The offset from the start of the <paramref name="ownedMemory" /> that the <paramref name="readOnlyMemory" /> represents.</param> + /// <param name="length">The length of the <paramref name="ownedMemory" /> that the <paramref name="readOnlyMemory" /> represents.</param> + /// <returns>A <see cref="bool"/> indicating if it was successful.</returns> + public static bool TryGetOwnedMemory<T, TOwner>(ReadOnlyMemory<T> readOnlyMemory, out TOwner ownedMemory, out int index, out int length) + where TOwner : OwnedMemory<T> + { + TOwner owner; // Use register for null comparison rather than byref + ownedMemory = owner = readOnlyMemory.GetObjectStartLength(out index, out length) as TOwner; + index &= ReadOnlyMemory<T>.RemoveOwnedFlagBitMask; + return !ReferenceEquals(owner, null); + } + + /// <summary> /// Creates an <see cref="IEnumerable{T}"/> view of the given <paramref name="memory" /> to allow /// the <paramref name="memory" /> to be used in existing APIs that take an <see cref="IEnumerable{T}"/>. /// </summary> 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 e0d4ec322..0ae1922fd 100644 --- a/src/System.Private.CoreLib/shared/System/Span.Fast.cs +++ b/src/System.Private.CoreLib/shared/System/Span.Fast.cs @@ -23,7 +23,7 @@ namespace System /// or native memory, or to memory allocated on the stack. It is type- and memory-safe. /// </summary> [DebuggerTypeProxy(typeof(SpanDebugView<>))] - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{ToString(),raw}")] [NonVersionable] public readonly ref partial struct Span<T> { @@ -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 (<0 or >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 (<0 or >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/Span.cs b/src/System.Private.CoreLib/shared/System/Span.cs index 106c0f7d5..78733515c 100644 --- a/src/System.Private.CoreLib/shared/System/Span.cs +++ b/src/System.Private.CoreLib/shared/System/Span.cs @@ -18,7 +18,7 @@ namespace System /// or native memory, or to memory allocated on the stack. It is type- and memory-safe. /// </summary> [DebuggerTypeProxy(typeof(SpanDebugView<>))] - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{ToString(),raw}")] public readonly ref partial struct Span<T> { /// <summary> 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 f829bd30a..76cbfaa8d 100644 --- a/src/System.Private.CoreLib/shared/System/String.Manipulation.cs +++ b/src/System.Private.CoreLib/shared/System/String.Manipulation.cs @@ -17,13 +17,6 @@ namespace System { private const int StackallocIntBufferSizeLimit = 128; - // Workaround for https://github.com/dotnet/coreclr/issues/16197 - [StructLayout(LayoutKind.Sequential, Size = StackallocIntBufferSizeLimit * sizeof(int))] - struct StackallocIntBuffer - { - private int _dummy; - } - private static unsafe void FillStringChecked(string dest, int destPos, string src) { Debug.Assert(dest != null); @@ -1079,9 +1072,7 @@ namespace System if (newValue == null) newValue = string.Empty; - // Workaround for https://github.com/dotnet/coreclr/issues/16197 - // Span<int> initialSpan = stackalloc int[StackallocIntBufferSizeLimit]; - Span<int> initialSpan; StackallocIntBuffer initialBuffer; unsafe { initialSpan = new Span<int>(&initialBuffer, StackallocIntBufferSizeLimit); } + Span<int> initialSpan = stackalloc int[StackallocIntBufferSizeLimit]; var replacementIndices = new ValueListBuilder<int>(initialSpan); unsafe @@ -1116,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(); @@ -1145,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; } @@ -1237,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) @@ -1318,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) @@ -1343,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/StringComparer.cs b/src/System.Private.CoreLib/shared/System/StringComparer.cs index b5f20c957..de311e91d 100644 --- a/src/System.Private.CoreLib/shared/System/StringComparer.cs +++ b/src/System.Private.CoreLib/shared/System/StringComparer.cs @@ -179,15 +179,17 @@ namespace System private readonly CompareInfo _compareInfo; // Do not rename (binary serialization) private CompareOptions _options; - internal CultureAwareComparer(CultureInfo culture, CompareOptions compareOptions) + internal CultureAwareComparer(CultureInfo culture, CompareOptions options) : this(culture.CompareInfo, options) { } + + internal CultureAwareComparer(CompareInfo compareInfo, CompareOptions options) { - _compareInfo = culture.CompareInfo; + _compareInfo = compareInfo; - if ((compareOptions & CultureAwareComparer.ValidCompareMaskOffFlags) != 0) + if ((options & ValidCompareMaskOffFlags) != 0) { - throw new ArgumentException(SR.Argument_InvalidFlag, nameof(compareOptions)); + throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); } - _options = compareOptions; + _options = options; } private CultureAwareComparer(SerializationInfo info, StreamingContext context) 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 99045ca63..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 { @@ -26,37 +27,51 @@ namespace System.Text get => _pos; set { - int delta = value - _pos; - if (delta > 0) - { - Append('\0', delta); - } - else - { - _pos = value; - } + 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] + { + get + { + Debug.Assert(index < _pos); + return ref _chars[index]; } } public override string ToString() { var s = new string(_chars.Slice(0, _pos)); - Clear(); + Dispose(); 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)) { charsWritten = _pos; - Clear(); + Dispose(); return true; } else { charsWritten = 0; - Clear(); + Dispose(); return false; } } @@ -112,7 +127,7 @@ namespace System.Text Grow(s.Length); } - s.AsReadOnlySpan().CopyTo(_chars.Slice(pos)); + s.AsSpan().CopyTo(_chars.Slice(pos)); _pos += s.Length; } @@ -147,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) { @@ -170,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)); @@ -185,7 +212,7 @@ namespace System.Text } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Clear() + public void Dispose() { char[] toReturn = _arrayToReturnToPool; this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again 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/Internal/Runtime/Augments/InteropCallbacks.cs b/src/System.Private.CoreLib/src/Internal/Runtime/Augments/InteropCallbacks.cs index f215c7304..fb982fec8 100644 --- a/src/System.Private.CoreLib/src/Internal/Runtime/Augments/InteropCallbacks.cs +++ b/src/System.Private.CoreLib/src/Internal/Runtime/Augments/InteropCallbacks.cs @@ -12,7 +12,9 @@ namespace Internal.Runtime.Augments [System.Runtime.CompilerServices.ReflectionBlocked] public abstract class InteropCallbacks { - public abstract bool TryGetMarshallerDataForDelegate(RuntimeTypeHandle delegateTypeHandle, out McgPInvokeDelegateData delegateData); + public abstract IntPtr GetForwardDelegateCreationStub(RuntimeTypeHandle delegateTypeHandle); + + public abstract IntPtr GetDelegateMarshallingStub(RuntimeTypeHandle delegateTypeHandle, bool openStaticDelegate); public abstract bool TryGetStructUnmarshalStub(RuntimeTypeHandle structureTypeHandle, out IntPtr unmarshalStub); diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs b/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs index 90a4c5156..ad79c2510 100644 --- a/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs +++ b/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs @@ -339,20 +339,15 @@ namespace Internal.Runtime.CompilerHelpers { PInvokeMarshal.CoTaskMemFree((IntPtr)p); } - /// <summary> - /// Returns the stub to the pinvoke marshalling stub - /// </summary> - public static IntPtr GetStubForPInvokeDelegate(Delegate del) + + public static IntPtr GetFunctionPointerForDelegate(Delegate del) { - return PInvokeMarshal.GetStubForPInvokeDelegate(del); + return PInvokeMarshal.GetFunctionPointerForDelegate(del); } - /// <summary> - /// Retrieve the corresponding P/invoke instance from the stub - /// </summary> - public static Delegate GetPInvokeDelegateForStub(IntPtr pStub, RuntimeTypeHandle delegateType) + public static Delegate GetDelegateForFunctionPointer(IntPtr ptr, RuntimeTypeHandle delegateType) { - return PInvokeMarshal.GetPInvokeDelegateForStub(pStub, delegateType); + return PInvokeMarshal.GetDelegateForFunctionPointer(ptr, delegateType); } /// <summary> 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.Private.CoreLib.csproj b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 71b536f49..bd1371f69 100644 --- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -263,6 +263,7 @@ <Compile Include="System\Runtime\InteropServices\GCHandleType.cs" /> <Compile Include="System\Runtime\InteropServices\InteropExtensions.cs" /> <Compile Include="System\Runtime\InteropServices\NativeCallableAttribute.cs" /> + <Compile Include="System\Runtime\InteropServices\NativeFunctionPointerWrapper.cs" /> <Compile Include="System\Runtime\InteropServices\PInvokeMarshal.cs" /> <Compile Include="System\Runtime\InteropServices\Marshal.cs" /> <Compile Include="System\Runtime\InteropServices\SafeHandle.cs" /> @@ -676,9 +677,6 @@ <Compile Include="..\..\Common\src\System\Runtime\InteropServices\McgGeneratedNativeCallCodeAttribute.cs"> <Link>System\Runtime\InteropServices\McgGeneratedNativeCallCodeAttribute.cs</Link> </Compile> - <Compile Include="..\..\Common\src\System\Runtime\InteropServices\McgPInvokeData.cs"> - <Link>System\Runtime\InteropServices\McgPInvokeData.cs</Link> - </Compile> <Compile Include="..\..\Common\src\System\NotImplemented.cs"> <Link>System\NotImplemented.cs</Link> </Compile> 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/MulticastDelegate.cs b/src/System.Private.CoreLib/src/System/MulticastDelegate.cs index 7368f2a15..a3a23e67a 100644 --- a/src/System.Private.CoreLib/src/System/MulticastDelegate.cs +++ b/src/System.Private.CoreLib/src/System/MulticastDelegate.cs @@ -118,18 +118,22 @@ namespace System public static bool operator ==(MulticastDelegate d1, MulticastDelegate d2) { - if ((Object)d1 == null) - return (Object)d2 == null; + if (ReferenceEquals(d1, d2)) + { + return true; + } - return d1.Equals(d2); + return d1 is null ? false : d1.Equals(d2); } public static bool operator !=(MulticastDelegate d1, MulticastDelegate d2) { - if ((Object)d1 == null) - return (Object)d2 != null; + if (ReferenceEquals(d1, d2)) + { + return false; + } - return !d1.Equals(d2); + return d1 is null ? true : !d1.Equals(d2); } public override sealed Delegate[] GetInvocationList() 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/InteropServices/InteropExtensions.cs b/src/System.Private.CoreLib/src/System/Runtime/InteropServices/InteropExtensions.cs index 269d8b434..b296c66fd 100644 --- a/src/System.Private.CoreLib/src/System/Runtime/InteropServices/InteropExtensions.cs +++ b/src/System.Private.CoreLib/src/System/Runtime/InteropServices/InteropExtensions.cs @@ -184,8 +184,7 @@ namespace System.Runtime.InteropServices public static bool IsDelegate(this RuntimeTypeHandle handle) { - return InteropExtensions.AreTypesAssignable(handle, typeof(MulticastDelegate).TypeHandle) || - InteropExtensions.AreTypesAssignable(handle, typeof(Delegate).TypeHandle); + return InteropExtensions.AreTypesAssignable(handle, typeof(Delegate).TypeHandle); } public static bool AreTypesAssignable(RuntimeTypeHandle sourceType, RuntimeTypeHandle targetType) diff --git a/src/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.cs b/src/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.cs index 6fc444351..5af9ce674 100644 --- a/src/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.cs +++ b/src/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.cs @@ -2,6 +2,7 @@ // 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.Runtime.Augments; namespace System.Runtime.InteropServices { @@ -72,5 +73,11 @@ namespace System.Runtime.InteropServices return PInvokeMarshal.PtrToStringAnsi(ptr, len); } #endif + + public static void ThrowExceptionForHR(int errorCode) + { + if (errorCode < 0) + throw RuntimeAugments.Callbacks.GetExceptionForHR(errorCode); + } } } diff --git a/src/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeFunctionPointerWrapper.cs b/src/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeFunctionPointerWrapper.cs new file mode 100644 index 000000000..d259223ff --- /dev/null +++ b/src/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeFunctionPointerWrapper.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + /// <summary> + /// Base class for all 'wrapper' classes that wraps a native function pointer + /// The forward delegates (that wraps native function pointers) points to derived Invoke method of this + /// class, and the Invoke method would implement the marshalling and making the call + /// </summary> + public abstract class NativeFunctionPointerWrapper + { + public NativeFunctionPointerWrapper(IntPtr nativeFunctionPointer) + { + m_nativeFunctionPointer = nativeFunctionPointer; + } + + IntPtr m_nativeFunctionPointer; + + public IntPtr NativeFunctionPointer + { + get { return m_nativeFunctionPointer; } + } + } +} diff --git a/src/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs b/src/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs index 99eaf7eda..bca69bce3 100644 --- a/src/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs +++ b/src/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs @@ -186,7 +186,7 @@ namespace System.Runtime.InteropServices /// Return the stub to the pinvoke marshalling stub /// </summary> /// <param name="del">The delegate</param> - public static IntPtr GetStubForPInvokeDelegate(Delegate del) + public static IntPtr GetFunctionPointerForDelegate(Delegate del) { if (del == null) return IntPtr.Zero; @@ -314,16 +314,13 @@ namespace System.Runtime.InteropServices var delegateThunk = new PInvokeDelegateThunk(del); - McgPInvokeDelegateData pinvokeDelegateData; - if (!RuntimeAugments.InteropCallbacks.TryGetMarshallerDataForDelegate(del.GetTypeHandle(), out pinvokeDelegateData)) - { - Environment.FailFast("Couldn't find marshalling stubs for delegate."); - } - // // For open static delegates set target to ReverseOpenStaticDelegateStub which calls the static function pointer directly // - IntPtr pTarget = del.GetRawFunctionPointerForOpenStaticDelegate() == IntPtr.Zero ? pinvokeDelegateData.ReverseStub : pinvokeDelegateData.ReverseOpenStaticDelegateStub; + bool openStaticDelegate = del.GetRawFunctionPointerForOpenStaticDelegate() != IntPtr.Zero; + + IntPtr pTarget = RuntimeAugments.InteropCallbacks.GetDelegateMarshallingStub(del.GetTypeHandle(), openStaticDelegate); + Debug.Assert(pTarget != IntPtr.Zero); RuntimeAugments.SetThunkData(s_thunkPoolHeap, delegateThunk.Thunk, delegateThunk.ContextData, pTarget); @@ -333,9 +330,9 @@ namespace System.Runtime.InteropServices /// <summary> /// Retrieve the corresponding P/invoke instance from the stub /// </summary> - public static Delegate GetPInvokeDelegateForStub(IntPtr pStub, RuntimeTypeHandle delegateType) + public static Delegate GetDelegateForFunctionPointer(IntPtr ptr, RuntimeTypeHandle delegateType) { - if (pStub == IntPtr.Zero) + if (ptr == IntPtr.Zero) return null; // // First try to see if this is one of the thunks we've allocated when we marshal a managed @@ -344,7 +341,7 @@ namespace System.Runtime.InteropServices // IntPtr pContext; IntPtr pTarget; - if (s_thunkPoolHeap != null && RuntimeAugments.TryGetThunkData(s_thunkPoolHeap, pStub, out pContext, out pTarget)) + if (s_thunkPoolHeap != null && RuntimeAugments.TryGetThunkData(s_thunkPoolHeap, ptr, out pContext, out pTarget)) { GCHandle handle; unsafe @@ -352,7 +349,7 @@ namespace System.Runtime.InteropServices // Pull out Handle from context handle = ((ThunkContextData*)pContext)->Handle; } - Delegate target = InteropExtensions.UncheckedCast<Delegate>(handle.Target); + Delegate target = Unsafe.As<Delegate>(handle.Target); // // The delegate might already been garbage collected @@ -372,15 +369,10 @@ namespace System.Runtime.InteropServices // We need to create the delegate that points to the invoke method of a // NativeFunctionPointerWrapper derived class // - McgPInvokeDelegateData pInvokeDelegateData; - if (!RuntimeAugments.InteropCallbacks.TryGetMarshallerDataForDelegate(delegateType, out pInvokeDelegateData)) - { - return null; - } - return CalliIntrinsics.Call<Delegate>( - pInvokeDelegateData.ForwardDelegateCreationStub, - pStub - ); + IntPtr pDelegateCreationStub = RuntimeAugments.InteropCallbacks.GetForwardDelegateCreationStub(delegateType); + Debug.Assert(pDelegateCreationStub != IntPtr.Zero); + + return CalliIntrinsics.Call<Delegate>(pDelegateCreationStub, ptr); } /// <summary> @@ -429,7 +421,7 @@ namespace System.Runtime.InteropServices } - T target = InteropExtensions.UncheckedCast<T>(handle.Target); + T target = Unsafe.As<T>(handle.Target); // // The delegate might already been garbage collected 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.CoreRT.cs b/src/System.Private.Interop/src/Internal/Runtime/CompilerHelpers/RuntimeInteropData.CoreRT.cs index 4de8b4603..afd236171 100644 --- a/src/System.Private.Interop/src/Internal/Runtime/CompilerHelpers/RuntimeInteropData.CoreRT.cs +++ b/src/System.Private.Interop/src/Internal/Runtime/CompilerHelpers/RuntimeInteropData.CoreRT.cs @@ -13,23 +13,25 @@ namespace Internal.Runtime.CompilerHelpers { internal partial class RuntimeInteropData { - public override bool TryGetMarshallerDataForDelegate(RuntimeTypeHandle delegateTypeHandle, out McgPInvokeDelegateData data) + public override IntPtr GetForwardDelegateCreationStub(RuntimeTypeHandle delegateTypeHandle) { IntPtr openStub, closedStub, delegateCreationStub; - if (!TryGetMarshallersForDelegate(delegateTypeHandle, out openStub, out closedStub, out delegateCreationStub)) - { - data = default(McgPInvokeDelegateData); - return false; - } + GetMarshallersForDelegate(delegateTypeHandle, out openStub, out closedStub, out delegateCreationStub); + if (delegateCreationStub == IntPtr.Zero) + throw new MissingInteropDataException(SR.DelegateMarshalling_MissingInteropData, Type.GetTypeFromHandle(delegateTypeHandle)); + return delegateCreationStub; + } - data = new global::System.Runtime.InteropServices.McgPInvokeDelegateData() - { - ReverseOpenStaticDelegateStub = openStub, - ReverseStub = closedStub, - ForwardDelegateCreationStub = delegateCreationStub - }; - return true; + public override IntPtr GetDelegateMarshallingStub(RuntimeTypeHandle delegateTypeHandle, bool openStaticDelegate) + { + IntPtr openStub, closedStub, delegateCreationStub; + GetMarshallersForDelegate(delegateTypeHandle, out openStub, out closedStub, out delegateCreationStub); + IntPtr pStub = openStaticDelegate ? openStub : closedStub; + if (pStub == IntPtr.Zero) + throw new MissingInteropDataException(SR.DelegateMarshalling_MissingInteropData, Type.GetTypeFromHandle(delegateTypeHandle)); + return pStub; } + #region "Struct Data" public override bool TryGetStructUnmarshalStub(RuntimeTypeHandle structureTypeHandle, out IntPtr unmarshalStub) { @@ -112,7 +114,7 @@ namespace Internal.Runtime.CompilerHelpers return false; } - private unsafe bool TryGetMarshallersForDelegate(RuntimeTypeHandle delegateTypeHandle, out IntPtr openStub, out IntPtr closedStub, out IntPtr delegateCreationStub) + private unsafe bool GetMarshallersForDelegate(RuntimeTypeHandle delegateTypeHandle, out IntPtr openStub, out IntPtr closedStub, out IntPtr delegateCreationStub) { int delegateHashcode = delegateTypeHandle.GetHashCode(); openStub = IntPtr.Zero; 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 84678d1b9..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 @@ -14,9 +14,22 @@ namespace Internal.Runtime.CompilerHelpers { internal partial class RuntimeInteropData : InteropCallbacks { - public override bool TryGetMarshallerDataForDelegate(RuntimeTypeHandle delegateTypeHandle, out McgPInvokeDelegateData data) + public override IntPtr GetForwardDelegateCreationStub(RuntimeTypeHandle delegateTypeHandle) { - return McgModuleManager.GetPInvokeDelegateData(delegateTypeHandle, out data); + McgModuleManager.GetPInvokeDelegateData(delegateTypeHandle, out McgPInvokeDelegateData data); + IntPtr pStub = data.ForwardDelegateCreationStub; + if (pStub == IntPtr.Zero) + throw new MissingInteropDataException(SR.DelegateMarshalling_MissingInteropData, Type.GetTypeFromHandle(delegateTypeHandle)); + return pStub; + } + + public override IntPtr GetDelegateMarshallingStub(RuntimeTypeHandle delegateTypeHandle, bool openStaticDelegate) + { + McgModuleManager.GetPInvokeDelegateData(delegateTypeHandle, out McgPInvokeDelegateData pinvokeDelegateData); + IntPtr pStub = openStaticDelegate ? pinvokeDelegateData.ReverseOpenStaticDelegateStub : pinvokeDelegateData.ReverseStub; + if (pStub == IntPtr.Zero) + throw new MissingInteropDataException(SR.DelegateMarshalling_MissingInteropData, Type.GetTypeFromHandle(delegateTypeHandle)); + return pStub; } #region "Struct Data" diff --git a/src/System.Private.Interop/src/InteropExtensions/PInvokeMarshal.cs b/src/System.Private.Interop/src/InteropExtensions/PInvokeMarshal.cs index 6162f29ca..4287d0e16 100644 --- a/src/System.Private.Interop/src/InteropExtensions/PInvokeMarshal.cs +++ b/src/System.Private.Interop/src/InteropExtensions/PInvokeMarshal.cs @@ -32,12 +32,12 @@ namespace System.Runtime.InteropServices s_lastWin32Error = Marshal.GetLastWin32Error(); } - public static IntPtr GetStubForPInvokeDelegate(Delegate del) + public static IntPtr GetFunctionPointerForDelegate(Delegate del) { return IntPtr.Zero; } - public static Delegate GetPInvokeDelegateForStub(IntPtr pStub, RuntimeTypeHandle delegateType) + public static Delegate GetDelegateForFunctionPointer(IntPtr pStub, RuntimeTypeHandle delegateType) { return default(Delegate); } diff --git a/src/System.Private.Interop/src/Shared/Marshal.cs b/src/System.Private.Interop/src/Shared/Marshal.cs index 4dfbd0bff..6a0be9a50 100644 --- a/src/System.Private.Interop/src/Shared/Marshal.cs +++ b/src/System.Private.Interop/src/Shared/Marshal.cs @@ -93,7 +93,8 @@ namespace System.Runtime.InteropServices createCOMException : true, hasErrorInfo: false); #else - return new Exception(errorCode.ToString()); + // TODO: Map HR to exeption even without COM interop support? + return new COMException(errorCode); #endif } } diff --git a/src/System.Private.Interop/src/Shared/McgMarshal.cs b/src/System.Private.Interop/src/Shared/McgMarshal.cs index bcd2166e4..d6ef590c2 100644 --- a/src/System.Private.Interop/src/Shared/McgMarshal.cs +++ b/src/System.Private.Interop/src/Shared/McgMarshal.cs @@ -1033,7 +1033,8 @@ namespace System.Runtime.InteropServices #elif CORECLR return Marshal.GetExceptionForHR(hr); #else - return new COMException(hr.ToString(), hr); + // TODO: Map HR to exeption even without COM interop support? + return new COMException(hr); #endif } @@ -1117,7 +1118,7 @@ namespace System.Runtime.InteropServices #if CORECLR throw new NotSupportedException(); #else - return PInvokeMarshal.GetStubForPInvokeDelegate(dele); + return PInvokeMarshal.GetFunctionPointerForDelegate(dele); #endif } @@ -1141,7 +1142,7 @@ namespace System.Runtime.InteropServices pStub ); #else - return PInvokeMarshal.GetPInvokeDelegateForStub(pStub, delegateType); + return PInvokeMarshal.GetDelegateForFunctionPointer(pStub, delegateType); #endif } diff --git a/src/System.Private.Interop/src/Shared/McgModuleManager.cs b/src/System.Private.Interop/src/Shared/McgModuleManager.cs index 0c7856dcb..365509a58 100644 --- a/src/System.Private.Interop/src/Shared/McgModuleManager.cs +++ b/src/System.Private.Interop/src/Shared/McgModuleManager.cs @@ -724,11 +724,7 @@ namespace System.Runtime.InteropServices return true; } } -#if ENABLE_WINRT - throw new MissingInteropDataException(SR.DelegateMarshalling_MissingInteropData, Type.GetTypeFromHandle(delegateType)); -#else return false; -#endif } #endregion diff --git a/src/Common/src/System/Runtime/InteropServices/McgPInvokeData.cs b/src/System.Private.Interop/src/Shared/McgPInvokeData.cs index ceaaa1e51..c0d01fce6 100644 --- a/src/Common/src/System/Runtime/InteropServices/McgPInvokeData.cs +++ b/src/System.Private.Interop/src/Shared/McgPInvokeData.cs @@ -102,25 +102,4 @@ namespace System.Runtime.InteropServices /// </summary> public IntPtr ForwardDelegateCreationStub; } - - - /// <summary> - /// Base class for all 'wrapper' classes that wraps a native function pointer - /// The forward delegates (that wraps native function pointers) points to derived Invoke method of this - /// class, and the Invoke method would implement the marshalling and making the call - /// </summary> - public abstract class NativeFunctionPointerWrapper - { - public NativeFunctionPointerWrapper(IntPtr nativeFunctionPointer) - { - m_nativeFunctionPointer = nativeFunctionPointer; - } - - IntPtr m_nativeFunctionPointer; - - public IntPtr NativeFunctionPointer - { - get { return m_nativeFunctionPointer; } - } - } } diff --git a/src/System.Private.Interop/src/System.Private.Interop.CoreCLR.csproj b/src/System.Private.Interop/src/System.Private.Interop.CoreCLR.csproj index 9bf3e3c89..00f7d24f4 100644 --- a/src/System.Private.Interop/src/System.Private.Interop.CoreCLR.csproj +++ b/src/System.Private.Interop/src/System.Private.Interop.CoreCLR.csproj @@ -50,12 +50,10 @@ <Compile Include="InteropExtensions\PreInitializedAttribute.cs" /> <Compile Include="InteropExtensions\PInvokeMarshal.cs" /> <Compile Include="Interop\Interop.PlatformNotSupported.cs" /> - <Compile Include="..\..\Common\src\System\Runtime\InteropServices\McgPInvokeData.cs"> - <Link>System\Runtime\InteropServices\McgPInvokeData.cs</Link> - </Compile> <Compile Include="..\..\Common\src\System\Runtime\InteropServices\McgGeneratedNativeCallCodeAttribute.cs"> <Link>System\Runtime\InteropServices\McgGeneratedNativeCallCodeAttribute.cs</Link> </Compile> + <Compile Include="System\Runtime\InteropServices\MarshalAdapter.cs" /> </ItemGroup> <ItemGroup Condition="'$(TargetsWindows)' == 'true'"> diff --git a/src/System.Private.Interop/src/System.Private.Interop.Mono.csproj b/src/System.Private.Interop/src/System.Private.Interop.Mono.csproj index 63be4cab3..04be68423 100644 --- a/src/System.Private.Interop/src/System.Private.Interop.Mono.csproj +++ b/src/System.Private.Interop/src/System.Private.Interop.Mono.csproj @@ -54,12 +54,10 @@ <Compile Include="InteropExtensions\PreInitializedAttribute.cs" /> <Compile Include="InteropExtensions\PInvokeMarshal.cs" /> <Compile Include="Interop\Interop.PlatformNotSupported.cs" /> - <Compile Include="..\..\Common\src\System\Runtime\InteropServices\McgPInvokeData.cs"> - <Link>System\Runtime\InteropServices\McgPInvokeData.cs</Link> - </Compile> <Compile Include="..\..\Common\src\System\Runtime\InteropServices\McgGeneratedNativeCallCodeAttribute.cs"> <Link>System\Runtime\InteropServices\McgGeneratedNativeCallCodeAttribute.cs</Link> </Compile> + <Compile Include="System\Runtime\InteropServices\MarshalAdapter.cs" /> </ItemGroup> <ItemGroup Condition="'$(TargetsWindows)' == 'true'"> diff --git a/src/System.Private.Interop/src/System.Private.Interop.Shared.projitems b/src/System.Private.Interop/src/System.Private.Interop.Shared.projitems index 394ff286f..a76881f51 100644 --- a/src/System.Private.Interop/src/System.Private.Interop.Shared.projitems +++ b/src/System.Private.Interop/src/System.Private.Interop.Shared.projitems @@ -11,8 +11,6 @@ <Compile Include="System\Runtime\InteropServices\ComWeakReferenceHelpers.cs" /> <Compile Include="System\Runtime\InteropServices\InteropEventProvider.cs" /> - <Compile Include="System\Runtime\InteropServices\MarshalAdapter.cs" /> - <Compile Include="System\Runtime\InteropServices\MarshalImpl.cs" /> <Compile Include="Shared\ComCallableObject.cs" /> <Compile Include="Shared\ComInterop.cs" /> @@ -38,6 +36,7 @@ <Compile Include="Shared\McgMarshal.cs" /> <Compile Include="Shared\McgModule.cs" /> <Compile Include="Shared\McgModuleManager.cs" /> + <Compile Include="Shared\McgPInvokeData.cs" /> <Compile Include="Shared\McgPInvokeMarshalStubAttribute.cs" /> <Compile Include="Shared\McgredirectedMethodAttribute.cs" /> <Compile Include="Shared\McgRedirectedTypeAttribute.cs" /> 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.Interop/src/System/Runtime/InteropServices/ComTypes/bindptr.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/bindptr.cs index 3405ef9a9..1dde60ab2 100644 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/bindptr.cs +++ b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/bindptr.cs @@ -9,10 +9,13 @@ namespace System.Runtime.InteropServices.ComTypes [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)] public struct BINDPTR { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] [FieldOffset(0)] public IntPtr lpfuncdesc; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] [FieldOffset(0)] public IntPtr lpvardesc; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] [FieldOffset(0)] public IntPtr lptcomp; } diff --git a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/dispparams.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/dispparams.cs index bea6e7c37..404c768ff 100644 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/dispparams.cs +++ b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/dispparams.cs @@ -9,7 +9,9 @@ namespace System.Runtime.InteropServices.ComTypes [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct DISPPARAMS { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] public IntPtr rgvarg; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] public IntPtr rgdispidNamedArgs; public int cArgs; public int cNamedArgs; diff --git a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/excepinfo.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/excepinfo.cs index bab6e5fa8..9a0187dda 100644 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/excepinfo.cs +++ b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/excepinfo.cs @@ -18,7 +18,9 @@ namespace System.Runtime.InteropServices.ComTypes [MarshalAs(UnmanagedType.BStr)] public String bstrHelpFile; public int dwHelpContext; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] public IntPtr pvReserved; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] public IntPtr pfnDeferredFillIn; public Int32 scode; } diff --git a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/formatetc.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/formatetc.cs index 2365cfe9a..b443c2b34 100644 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/formatetc.cs +++ b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/formatetc.cs @@ -10,6 +10,7 @@ namespace System.Runtime.InteropServices.ComTypes { [MarshalAs(UnmanagedType.U2)] public short cfFormat; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] public IntPtr ptd; [MarshalAs(UnmanagedType.U4)] public DVASPECT dwAspect; diff --git a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/funcdesc.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/funcdesc.cs index ab249c469..63e665339 100644 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/funcdesc.cs +++ b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/funcdesc.cs @@ -10,7 +10,9 @@ namespace System.Runtime.InteropServices.ComTypes public struct FUNCDESC { public int memid; //MEMBERID memid; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] public IntPtr lprgscode; // /* [size_is(cScodes)] */ SCODE RPC_FAR *lprgscode; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] public IntPtr lprgelemdescParam; // /* [size_is(cParams)] */ ELEMDESC __RPC_FAR *lprgelemdescParam; public FUNCKIND funckind; //FUNCKIND funckind; public INVOKEKIND invkind; //INVOKEKIND invkind; diff --git a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/idldesc.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/idldesc.cs index 189287924..31493355e 100644 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/idldesc.cs +++ b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/idldesc.cs @@ -9,6 +9,7 @@ namespace System.Runtime.InteropServices.ComTypes [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct IDLDESC { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] public IntPtr dwReserved; public IDLFLAG wIDLFlags; } diff --git a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/paramdesc.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/paramdesc.cs index bc0d107e1..809ce145c 100644 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/paramdesc.cs +++ b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/paramdesc.cs @@ -9,6 +9,7 @@ namespace System.Runtime.InteropServices.ComTypes [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct PARAMDESC { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] public IntPtr lpVarValue; public PARAMFLAG wParamFlags; } diff --git a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/stgmedium.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/stgmedium.cs index cdb8015ac..77505cd72 100644 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/stgmedium.cs +++ b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/stgmedium.cs @@ -9,6 +9,7 @@ namespace System.Runtime.InteropServices.ComTypes public struct STGMEDIUM { public TYMED tymed; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] public IntPtr unionmember; [MarshalAs(UnmanagedType.IUnknown)] public object pUnkForRelease; diff --git a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/typeattr.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/typeattr.cs index bf4488b72..3e6381192 100644 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/typeattr.cs +++ b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/typeattr.cs @@ -18,6 +18,7 @@ namespace System.Runtime.InteropServices.ComTypes public Int32 dwReserved; public Int32 memidConstructor; public Int32 memidDestructor; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] public IntPtr lpstrSchema; public Int32 cbSizeInstance; public TYPEKIND typekind; diff --git a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/typedesc.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/typedesc.cs index 29d9a2e9e..696f439ad 100644 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/typedesc.cs +++ b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/typedesc.cs @@ -9,6 +9,7 @@ namespace System.Runtime.InteropServices.ComTypes [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct TYPEDESC { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] public IntPtr lpValue; public Int16 vt; } diff --git a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/vardesc.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/vardesc.cs index 2a349f42a..abc4b70d7 100644 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/vardesc.cs +++ b/src/System.Private.Interop/src/System/Runtime/InteropServices/ComTypes/vardesc.cs @@ -17,6 +17,7 @@ namespace System.Runtime.InteropServices.ComTypes { [FieldOffset(0)] public int oInst; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification="Backwards compatibility")] [FieldOffset(0)] public IntPtr lpvarValue; }; diff --git a/src/System.Private.Interop/src/System/Runtime/InteropServices/Marshal.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/Marshal.cs index f9e481abc..d565e199f 100644 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/Marshal.cs +++ b/src/System.Private.Interop/src/System/Runtime/InteropServices/Marshal.cs @@ -2,13 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -// -// This file provides an implementation of the pieces of the Marshal class which are required by the Interop -// API contract but are not provided by the version of Marshal which is part of the Redhawk test library. -// This partial class is combined with the version from the Redhawk test library, in order to provide the -// Marshal implementation for System.Private.CoreLib. -// - using System; using System.Collections.Generic; using System.Diagnostics; @@ -575,7 +568,7 @@ namespace System.Runtime.InteropServices createCOMException: false, hasErrorInfo: false); #else - throw new PlatformNotSupportedException("GetExceptionForHR"); + return new COMException(errorCode); #endif // ENABLE_WINRT } @@ -763,7 +756,7 @@ namespace System.Runtime.InteropServices { throw new ArgumentNullException(nameof(o)); } - return MarshalAdapter.GetIUnknownForObject(o); + return McgMarshal.ObjectToComInterface(o, InternalTypes.IUnknown); } //==================================================================== @@ -775,7 +768,7 @@ namespace System.Runtime.InteropServices { throw new ArgumentNullException(nameof(pUnk)); } - return MarshalAdapter.GetObjectForIUnknown(pUnk); + return McgMarshal.ComInterfaceToObject(pUnk, InternalTypes.IUnknown); } //==================================================================== @@ -1018,7 +1011,7 @@ namespace System.Runtime.InteropServices if (d == null) throw new ArgumentNullException(nameof(d)); - return PInvokeMarshal.GetStubForPInvokeDelegate(d); + return PInvokeMarshal.GetFunctionPointerForDelegate(d); } public static IntPtr GetFunctionPointerForDelegate<TDelegate>(TDelegate d) @@ -1268,7 +1261,13 @@ namespace System.Runtime.InteropServices public static IntPtr /* IUnknown* */ GetComInterfaceForObject(Object o, Type T) { - return MarshalAdapter.GetComInterfaceForObject(o, T); + if (o == null) + throw new ArgumentNullException(nameof(o)); + + if (T == null) + throw new ArgumentNullException(nameof(T)); + + return McgMarshal.ObjectToComInterface(o, T.TypeHandle); } public static TDelegate GetDelegateForFunctionPointer<TDelegate>(IntPtr ptr) @@ -1288,13 +1287,12 @@ namespace System.Runtime.InteropServices if (t.TypeHandle.IsGenericType() || t.TypeHandle.IsGenericTypeDefinition()) throw new ArgumentException(SR.Argument_NeedNonGenericType, nameof(t)); - bool isDelegateType = InteropExtensions.AreTypesAssignable(t.TypeHandle, typeof(MulticastDelegate).TypeHandle) || - InteropExtensions.AreTypesAssignable(t.TypeHandle, typeof(Delegate).TypeHandle); + bool isDelegateType = InteropExtensions.AreTypesAssignable(t.TypeHandle, typeof(Delegate).TypeHandle); if (!isDelegateType) throw new ArgumentException(SR.Arg_MustBeDelegateType, nameof(t)); - return MarshalAdapter.GetDelegateForFunctionPointer(ptr, t); + return McgMarshal.GetPInvokeDelegateForStub(ptr, t.TypeHandle); } //==================================================================== diff --git a/src/System.Private.Interop/src/System/Runtime/InteropServices/MarshalAdapter.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/MarshalAdapter.cs index 599eca594..ecfe57bee 100644 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/MarshalAdapter.cs +++ b/src/System.Private.Interop/src/System/Runtime/InteropServices/MarshalAdapter.cs @@ -2,13 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -// -// This file provides an implementation of the pieces of the Marshal class which are required by the Interop -// API contract but are not provided by the version of Marshal which is part of the Redhawk test library. -// This partial class is combined with the version from the Redhawk test library, in order to provide the -// Marshal implementation for System.Private.CoreLib. -// - using System; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -36,7 +29,7 @@ namespace System.Runtime.InteropServices //==================================================================== public static IntPtr /* IUnknown* */ GetIUnknownForObject(Object o) { - return MarshalImpl.GetIUnknownForObject(o); + return McgMarshal.ObjectToComInterface(o, InternalTypes.IUnknown); } //==================================================================== @@ -44,7 +37,7 @@ namespace System.Runtime.InteropServices //==================================================================== public static Object GetObjectForIUnknown(IntPtr /* IUnknown* */ pUnk) { - object obj = MarshalImpl.GetObjectForIUnknown(pUnk); + object obj = McgMarshal.ComInterfaceToObject(pUnk, InternalTypes.IUnknown); #if CORECLR if (obj == null) { @@ -61,7 +54,13 @@ namespace System.Runtime.InteropServices public static IntPtr /* IUnknown* */ GetComInterfaceForObject(Object o, Type T) { - IntPtr ptr = MarshalImpl.GetComInterfaceForObject(o, T); + if (o == null) + throw new ArgumentNullException(nameof(o)); + + if (T == null) + throw new ArgumentNullException(nameof(T)); + + IntPtr ptr = McgMarshal.ObjectToComInterface(o, T.TypeHandle); #if CORECLR if (ptr == default(IntPtr)) { @@ -81,7 +80,7 @@ namespace System.Runtime.InteropServices //==================================================================== public static Delegate GetDelegateForFunctionPointer(IntPtr ptr, Type t) { - Delegate dlg = MarshalImpl.GetDelegateForFunctionPointer(ptr, t); + Delegate dlg = McgMarshal.GetPInvokeDelegateForStub(ptr, t.TypeHandle); #if CORECLR if (dlg == null) // fall back to public marshal API { @@ -93,32 +92,42 @@ namespace System.Runtime.InteropServices public static bool IsComObject(object o) { - return MarshalImpl.IsComObject(o); + if (o == null) + throw new ArgumentNullException(nameof(o)); + return McgMarshal.IsComObject(o); } public static int ReleaseComObject(object o) { - return MarshalImpl.ReleaseComObject(o); + if (o == null) + throw new ArgumentNullException(nameof(o)); + return McgMarshal.Release(o as __ComObject); } public static int FinalReleaseComObject(object o) { - return MarshalImpl.FinalReleaseComObject(o); + return McgMarshal.FinalReleaseComObject(o); } public static int QueryInterface(IntPtr pUnk, ref Guid iid, out IntPtr ppv) { - return MarshalImpl.QueryInterface(pUnk, ref iid, out ppv); + int hr = 0; + ppv = McgMarshal.ComQueryInterfaceNoThrow(pUnk, ref iid, out hr); +#if CORECLR + if (ppv == default(IntPtr)) + return Marshal.QueryInterface(pUnk, ref iid, out ppv); +#endif + return hr; } public static int AddRef(IntPtr pUnk) { - return MarshalImpl.AddRef(pUnk); + return McgMarshal.ComAddRef(pUnk); } public static int Release(IntPtr pUnk) { - return MarshalImpl.Release(pUnk); + return McgMarshal.ComRelease(pUnk); } } #pragma warning restore 618 diff --git a/src/System.Private.Interop/src/System/Runtime/InteropServices/MarshalImpl.cs b/src/System.Private.Interop/src/System/Runtime/InteropServices/MarshalImpl.cs deleted file mode 100644 index 20f08a919..000000000 --- a/src/System.Private.Interop/src/System/Runtime/InteropServices/MarshalImpl.cs +++ /dev/null @@ -1,76 +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; -using System.Runtime.CompilerServices; - -namespace System.Runtime.InteropServices -{ - internal static partial class MarshalImpl - { - public static IntPtr /* IUnknown* */ GetIUnknownForObject(Object o) - { - return McgMarshal.ObjectToComInterface(o, InternalTypes.IUnknown); - } - - public static Object GetObjectForIUnknown(IntPtr /* IUnknown* */ pUnk) - { - return McgMarshal.ComInterfaceToObject(pUnk, InternalTypes.IUnknown); - } - - public static Delegate GetDelegateForFunctionPointer(IntPtr ptr, Type t) - { - return McgMarshal.GetPInvokeDelegateForStub(ptr, t.TypeHandle); - } - public static IntPtr /* IUnknown* */ GetComInterfaceForObject(Object o, Type t) - { - if (o == null) - throw new ArgumentNullException(nameof(o)); - - if (t == null) - throw new ArgumentNullException(nameof(t)); - - return McgMarshal.ObjectToComInterface(o, t.TypeHandle); - } - - public static bool IsComObject(object o) - { - if (o == null) - throw new ArgumentNullException(nameof(o)); - return McgMarshal.IsComObject(o); - } - public static int ReleaseComObject(object o) - { - if (o == null) - throw new ArgumentNullException(nameof(o)); - return McgMarshal.Release(o as __ComObject); - } - - public static int FinalReleaseComObject(object o) - { - return McgMarshal.FinalReleaseComObject(o); - } - - public static int QueryInterface(IntPtr pUnk, ref Guid iid, out IntPtr ppv) - { - int hr = 0; - ppv = McgMarshal.ComQueryInterfaceNoThrow(pUnk, ref iid, out hr); -#if CORECLR - if (ppv == default(IntPtr)) - return Marshal.QueryInterface(pUnk, ref iid, out ppv); -#endif - return hr; - } - - public static int AddRef(IntPtr pUnk) - { - return McgMarshal.ComAddRef(pUnk); - } - - public static int Release(IntPtr pUnk) - { - return McgMarshal.ComRelease(pUnk); - } - } -} 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 65f51f471..bc9ce5cad 100644 --- a/src/dirs.proj +++ b/src/dirs.proj @@ -6,18 +6,15 @@ <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" /> <Project Include="Framework\Framework-uapaot.depproj" /> <Project Include="Framework\Framework-native.depproj" Condition="$(TargetsUnix) and '$(Platform)' != 'wasm'" /> - <Project Include="ILCompiler\**\*.depproj" /> + <Project Include="ILCompiler\RyuJIT\RyuJIT.depproj" /> + <Project Include="ILCompiler\ObjectWriter\ObjectWriter.depproj" /> + <Project Include="ILCompiler.WebAssembly\src\libLLVMdep.depproj" /> <Project Include="*\src\*.csproj" Exclude="@(ExcludeProjects)" /> <Project Include="*\src\*.vbproj" Condition="'$(IncludeVbProjects)'!='false'" Exclude="@(ExcludeProjects)" /> |