diff options
Diffstat (limited to 'src')
91 files changed, 2576 insertions, 1060 deletions
diff --git a/src/BuildIntegration/Microsoft.NETCore.Native.targets b/src/BuildIntegration/Microsoft.NETCore.Native.targets index 23b817c7a..0e3d59ec4 100644 --- a/src/BuildIntegration/Microsoft.NETCore.Native.targets +++ b/src/BuildIntegration/Microsoft.NETCore.Native.targets @@ -224,7 +224,7 @@ See the LICENSE file in the project root for more information. <Exec Command="$(CppLibCreator) @"$(NativeIntermediateOutputPath)lib.rsp"" Condition="'$(OS)' == 'Windows_NT' and '$(NativeLib)' == 'Static' and '$(NativeCodeGen)' != 'wasm'" /> <PropertyGroup> - <EmccArgs>"$(NativeObject)" -o "$(NativeBinary)" -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 </EmccArgs> + <EmccArgs>"$(NativeObject)" -o "$(NativeBinary)" -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 --emrun </EmccArgs> <EmccArgs Condition="'$(Platform)'=='wasm'">$(EmccArgs) "$(IlcPath)/sdk/libPortableRuntime.bc" "$(IlcPath)/sdk/libbootstrappercpp.bc" </EmccArgs> <EmccArgs Condition="'$(Configuration)'=='Release'">$(EmccArgs) -O2 --llvm-lto 2</EmccArgs> <EmccArgs Condition="'$(Configuration)'=='Debug'">$(EmccArgs) -g3</EmccArgs> diff --git a/src/Common/src/TypeSystem/Common/TargetDetails.cs b/src/Common/src/TypeSystem/Common/TargetDetails.cs index 31323e59b..a17a5157a 100644 --- a/src/Common/src/TypeSystem/Common/TargetDetails.cs +++ b/src/Common/src/TypeSystem/Common/TargetDetails.cs @@ -16,9 +16,10 @@ namespace Internal.TypeSystem ARM, ARMEL, ARM64, + Cpp64, X64, X86, - Wasm32 + Wasm32, } /// <summary> @@ -90,6 +91,7 @@ namespace Internal.TypeSystem { case TargetArchitecture.ARM64: case TargetArchitecture.X64: + case TargetArchitecture.Cpp64: return 8; case TargetArchitecture.ARM: case TargetArchitecture.ARMEL: @@ -204,7 +206,6 @@ namespace Internal.TypeSystem { case TargetArchitecture.ARM: case TargetArchitecture.ARMEL: - case TargetArchitecture.Wasm32: // ARM supports two alignments for objects on the GC heap (4 byte and 8 byte) if (fieldAlignment.IsIndeterminate) return LayoutInt.Indeterminate; @@ -215,8 +216,10 @@ namespace Internal.TypeSystem return new LayoutInt(8); case TargetArchitecture.X64: case TargetArchitecture.ARM64: + case TargetArchitecture.Cpp64: return new LayoutInt(8); case TargetArchitecture.X86: + case TargetArchitecture.Wasm32: return new LayoutInt(4); default: throw new NotSupportedException(); diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs index 5c93df610..8df68bd1d 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs @@ -906,7 +906,12 @@ namespace ILCompiler.DependencyAnalysis flags |= (uint)EETypeRareFlags.RequiresAlign8Flag; } - if (metadataType != null && metadataType.IsHfa) + TargetArchitecture targetArch = _type.Context.Target.Architecture; + if (metadataType != null && + (targetArch == TargetArchitecture.ARM || + targetArch == TargetArchitecture.ARMEL || + targetArch == TargetArchitecture.ARM64) && + metadataType.IsHfa) { flags |= (uint)EETypeRareFlags.IsHFAFlag; } diff --git a/src/ILCompiler.CppCodeGen/src/Compiler/DependencyAnalysis/CppCodegenNodeFactory.cs b/src/ILCompiler.CppCodeGen/src/Compiler/DependencyAnalysis/CppCodegenNodeFactory.cs index baf0339aa..d282e1f27 100644 --- a/src/ILCompiler.CppCodeGen/src/Compiler/DependencyAnalysis/CppCodegenNodeFactory.cs +++ b/src/ILCompiler.CppCodeGen/src/Compiler/DependencyAnalysis/CppCodegenNodeFactory.cs @@ -40,8 +40,7 @@ namespace ILCompiler.DependencyAnalysis protected override IMethodNode CreateUnboxingStubNode(MethodDesc method) { - // TODO: this is wrong: this returns an assembly stub node - return new UnboxingStubNode(method, Target); + return new CppUnboxingStubNode(method); } protected override ISymbolNode CreateReadyToRunHelperNode(ReadyToRunHelperKey helperCall) diff --git a/src/ILCompiler.CppCodeGen/src/Compiler/DependencyAnalysis/CppUnboxingStubNode.cs b/src/ILCompiler.CppCodeGen/src/Compiler/DependencyAnalysis/CppUnboxingStubNode.cs new file mode 100644 index 000000000..916c94d9e --- /dev/null +++ b/src/ILCompiler.CppCodeGen/src/Compiler/DependencyAnalysis/CppUnboxingStubNode.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; + +using ILCompiler.DependencyAnalysisFramework; + +using Internal.Text; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + internal class CppUnboxingStubNode : DependencyNodeCore<NodeFactory>, IMethodNode + { + public CppUnboxingStubNode(MethodDesc method) + { + Debug.Assert(method.OwningType.IsValueType && !method.Signature.IsStatic); + Method = method; + } + + public MethodDesc Method { get; } + + public int ClassCode => 17864523; + + public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append("unbox_").Append(nameMangler.GetMangledMethodName(Method)); + } + + public int CompareToImpl(ISortableSymbolNode other, CompilerComparer comparer) + { + return comparer.Compare(this.Method, ((CppUnboxingStubNode)other).Method); + } + + public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory) + { + return new DependencyListEntry[] { + new DependencyListEntry(factory.MethodEntrypoint(Method), "Target of unboxing") }; + } + + protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler); + + public static string GetMangledName(NameMangler nameMangler, MethodDesc method) + { + return "unbox_" + nameMangler.GetMangledMethodName(method); + } + + public override bool StaticDependenciesAreComputed => true; + public override bool HasDynamicDependencies => false; + public override bool InterestingForDynamicDependencyAnalysis => false; + public override bool HasConditionalStaticDependencies => false; + + public int Offset => throw new System.NotImplementedException(); + + public bool RepresentsIndirectionCell => throw new System.NotImplementedException(); + + public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) + { + return null; + } + public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory context) + { + return null; + } + } +} diff --git a/src/ILCompiler.CppCodeGen/src/CppCodeGen/CppWriter.cs b/src/ILCompiler.CppCodeGen/src/CppCodeGen/CppWriter.cs index be4301303..fb2130704 100644 --- a/src/ILCompiler.CppCodeGen/src/CppCodeGen/CppWriter.cs +++ b/src/ILCompiler.CppCodeGen/src/CppCodeGen/CppWriter.cs @@ -316,7 +316,13 @@ namespace ILCompiler.CppCodeGen public string GetCppFieldName(FieldDesc field) { - return _compilation.NameMangler.GetMangledFieldName(field).ToString(); + string name = _compilation.NameMangler.GetMangledFieldName(field).ToString(); + + // TODO: name mangling robustness + if (name == "register") + name = "_" + name + "_"; + + return name; } public string GetCppStaticFieldName(FieldDesc field) @@ -329,7 +335,7 @@ namespace ILCompiler.CppCodeGen public string SanitizeCppVarName(string varName) { // TODO: name mangling robustness - if (varName == "errno" || varName == "environ" || varName == "template" || varName == "typename") // some names collide with CRT headers + if (varName == "errno" || varName == "environ" || varName == "template" || varName == "typename" || varName == "register") // some names collide with CRT headers return "_" + varName + "_"; return _compilation.NameMangler.SanitizeName(varName); @@ -888,12 +894,12 @@ namespace ILCompiler.CppCodeGen { relocCode.Append("dispatchMapModule"); } - else if(reloc.Target is UnboxingStubNode) + else if(reloc.Target is CppUnboxingStubNode) { - var method = reloc.Target as UnboxingStubNode; + var method = reloc.Target as CppUnboxingStubNode; relocCode.Append("(void*)&"); - relocCode.Append(GetCppMethodDeclarationName(method.Method.OwningType, UnboxingStubNode.GetMangledName(factory.NameMangler, method.Method), false)); + relocCode.Append(GetCppMethodDeclarationName(method.Method.OwningType, method.GetMangledName(factory.NameMangler), false)); } else { @@ -1196,23 +1202,15 @@ namespace ILCompiler.CppCodeGen if (typeNode is ConstructedEETypeNode) { - IReadOnlyList<MethodDesc> virtualSlots = _compilation.NodeFactory.VTable(nodeType.GetClosestDefType()).Slots; + DefType closestDefType = nodeType.GetClosestDefType(); - int baseSlots = 0; - var baseType = nodeType.BaseType; - while (baseType != null) - { - IReadOnlyList<MethodDesc> baseVirtualSlots = _compilation.NodeFactory.VTable(baseType).Slots; - if (baseVirtualSlots != null) - baseSlots += baseVirtualSlots.Count; - baseType = baseType.BaseType; - } + IReadOnlyList<MethodDesc> virtualSlots = _compilation.NodeFactory.VTable(closestDefType).Slots; - for (int slot = 0; slot < virtualSlots.Count; slot++) + foreach (MethodDesc slot in virtualSlots) { - MethodDesc virtualMethod = virtualSlots[slot]; typeDefinitions.AppendLine(); - typeDefinitions.Append(GetCodeForVirtualMethod(virtualMethod, baseSlots + slot)); + int slotNumber = VirtualMethodSlotHelper.GetVirtualMethodSlot(_compilation.NodeFactory, slot, closestDefType); + typeDefinitions.Append(GetCodeForVirtualMethod(slot, slotNumber)); } if (nodeType.IsDelegate) @@ -1236,7 +1234,7 @@ namespace ILCompiler.CppCodeGen typeDefinitions.AppendLine(); AppendCppMethodDeclaration(typeDefinitions, m, false); typeDefinitions.AppendLine(); - AppendCppMethodDeclaration(typeDefinitions, m, false, null, null, UnboxingStubNode.GetMangledName(factory.NameMangler, m)); + AppendCppMethodDeclaration(typeDefinitions, m, false, null, null, CppUnboxingStubNode.GetMangledName(factory.NameMangler, m)); } } @@ -1336,13 +1334,13 @@ namespace ILCompiler.CppCodeGen /// </summary> /// <param name="unboxingStubNode">The unboxing stub node to be output</param> /// <param name="methodImplementations">The buffer in which to write out the C++ code</param> - private void OutputUnboxingStubNode(UnboxingStubNode unboxingStubNode) + private void OutputUnboxingStubNode(CppUnboxingStubNode unboxingStubNode) { Out.WriteLine(); CppGenerationBuffer sb = new CppGenerationBuffer(); sb.AppendLine(); - AppendCppMethodDeclaration(sb, unboxingStubNode.Method, true, null, null, UnboxingStubNode.GetMangledName(_compilation.NameMangler, unboxingStubNode.Method)); + AppendCppMethodDeclaration(sb, unboxingStubNode.Method, true, null, null, unboxingStubNode.GetMangledName(_compilation.NameMangler)); sb.AppendLine(); sb.Append("{"); sb.Indent(); @@ -1399,8 +1397,8 @@ namespace ILCompiler.CppCodeGen { if (node is CppMethodCodeNode) OutputMethodNode(node as CppMethodCodeNode); - else if (node is UnboxingStubNode) - OutputUnboxingStubNode(node as UnboxingStubNode); + else if (node is CppUnboxingStubNode) + OutputUnboxingStubNode(node as CppUnboxingStubNode); } Out.Dispose(); diff --git a/src/ILCompiler.CppCodeGen/src/CppCodeGen/ILToCppImporter.cs b/src/ILCompiler.CppCodeGen/src/CppCodeGen/ILToCppImporter.cs index ae062dad6..560820ad2 100644 --- a/src/ILCompiler.CppCodeGen/src/CppCodeGen/ILToCppImporter.cs +++ b/src/ILCompiler.CppCodeGen/src/CppCodeGen/ILToCppImporter.cs @@ -1151,7 +1151,7 @@ namespace Internal.IL { // TODO: Null checks - if (method.IsVirtual) + if (method.IsVirtual && !method.IsFinal && !method.OwningType.IsSealed()) { // TODO: Full resolution of virtual methods if (!method.IsNewSlot) diff --git a/src/ILCompiler.CppCodeGen/src/ILCompiler.CppCodeGen.csproj b/src/ILCompiler.CppCodeGen/src/ILCompiler.CppCodeGen.csproj index 1ef83f8bd..80a2a51f2 100644 --- a/src/ILCompiler.CppCodeGen/src/ILCompiler.CppCodeGen.csproj +++ b/src/ILCompiler.CppCodeGen/src/ILCompiler.CppCodeGen.csproj @@ -32,6 +32,7 @@ <Compile Include="Compiler\CppNodeMangler.cs" /> <Compile Include="Compiler\CppCodegenCompilation.cs" /> <Compile Include="Compiler\CppCodegenCompilationBuilder.cs" /> + <Compile Include="Compiler\DependencyAnalysis\CppUnboxingStubNode.cs" /> <Compile Include="CppCodeGen\DependencyNodeIterator.cs" /> <Compile Include="CppCodeGen\EvaluationStack.cs" /> <Compile Include="CppCodeGen\CppGenerationBuffer.cs" /> diff --git a/src/ILCompiler/src/Program.cs b/src/ILCompiler/src/Program.cs index e869f7540..b86796ad6 100644 --- a/src/ILCompiler/src/Program.cs +++ b/src/ILCompiler/src/Program.cs @@ -231,6 +231,9 @@ namespace ILCompiler if (_isWasmCodegen) _targetArchitecture = TargetArchitecture.Wasm32; + else if (_isCppCodegen) + _targetArchitecture = TargetArchitecture.Cpp64; + // // Initialize type system context // diff --git a/src/JitInterface/src/CorInfoTypes.cs b/src/JitInterface/src/CorInfoTypes.cs index b0ceda613..6d6653b85 100644 --- a/src/JitInterface/src/CorInfoTypes.cs +++ b/src/JitInterface/src/CorInfoTypes.cs @@ -1508,7 +1508,7 @@ namespace Internal.JitInterface CORJIT_FLAG_HAS_ARM64_LRCPC = 52, // ID_AA64ISAR1_EL1.LRCPC is 1 or better CORJIT_FLAG_HAS_ARM64_PMULL = 53, // ID_AA64ISAR0_EL1.AES is 2 or better CORJIT_FLAG_HAS_ARM64_SHA1 = 54, // ID_AA64ISAR0_EL1.SHA1 is 1 or better - CORJIT_FLAG_HAS_ARM64_SHA2 = 55, // ID_AA64ISAR0_EL1.SHA2 is 1 or better + CORJIT_FLAG_HAS_ARM64_SHA256 = 55, // ID_AA64ISAR0_EL1.SHA2 is 1 or better CORJIT_FLAG_HAS_ARM64_SHA512 = 56, // ID_AA64ISAR0_EL1.SHA2 is 2 or better CORJIT_FLAG_HAS_ARM64_SHA3 = 57, // ID_AA64ISAR0_EL1.SHA3 is 1 or better CORJIT_FLAG_HAS_ARM64_SIMD = 58, // ID_AA64PFR0_EL1.AdvSIMD is 0 or better diff --git a/src/JitInterface/src/ThunkGenerator/corinfo.h b/src/JitInterface/src/ThunkGenerator/corinfo.h index f3b509c5a..d430412f3 100644 --- a/src/JitInterface/src/ThunkGenerator/corinfo.h +++ b/src/JitInterface/src/ThunkGenerator/corinfo.h @@ -2375,7 +2375,7 @@ public: // This is only called for Value classes. It returns a boolean array // in representing of 'cls' from a GC perspective. The class is // assumed to be an array of machine words - // (of length // getClassSize(cls) / sizeof(void*)), + // (of length // getClassSize(cls) / TARGET_POINTER_SIZE), // 'gcPtrs' is a pointer to an array of BYTEs of this length. // getClassGClayout fills in this array so that gcPtrs[i] is set // to one of the CorInfoGCType values which is the GC type of diff --git a/src/JitInterface/src/ThunkGenerator/corjitflags.h b/src/JitInterface/src/ThunkGenerator/corjitflags.h index da303b6a0..84fb42f08 100644 --- a/src/JitInterface/src/ThunkGenerator/corjitflags.h +++ b/src/JitInterface/src/ThunkGenerator/corjitflags.h @@ -120,7 +120,7 @@ public: CORJIT_FLAG_HAS_ARM64_LRCPC = 52, // ID_AA64ISAR1_EL1.LRCPC is 1 or better CORJIT_FLAG_HAS_ARM64_PMULL = 53, // ID_AA64ISAR0_EL1.AES is 2 or better CORJIT_FLAG_HAS_ARM64_SHA1 = 54, // ID_AA64ISAR0_EL1.SHA1 is 1 or better - CORJIT_FLAG_HAS_ARM64_SHA2 = 55, // ID_AA64ISAR0_EL1.SHA2 is 1 or better + CORJIT_FLAG_HAS_ARM64_SHA256 = 55, // ID_AA64ISAR0_EL1.SHA2 is 1 or better CORJIT_FLAG_HAS_ARM64_SHA512 = 56, // ID_AA64ISAR0_EL1.SHA2 is 2 or better CORJIT_FLAG_HAS_ARM64_SHA3 = 57, // ID_AA64ISAR0_EL1.SHA3 is 1 or better CORJIT_FLAG_HAS_ARM64_SIMD = 58, // ID_AA64PFR0_EL1.AdvSIMD is 0 or better diff --git a/src/Common/src/Interop/Windows/mincore/Interop.CoCreateGuid.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Ole32/Interop.CoCreateGuid.cs index ac3bad582..60cfb23ea 100644 --- a/src/Common/src/Interop/Windows/mincore/Interop.CoCreateGuid.cs +++ b/src/System.Private.CoreLib/shared/Interop/Windows/Ole32/Interop.CoCreateGuid.cs @@ -7,9 +7,9 @@ using System.Runtime.InteropServices; internal static partial class Interop { - internal static partial class mincore + internal static partial class Ole32 { - [DllImport("api-ms-win-core-com-l1-1-0.dll")] - internal extern static int CoCreateGuid(out Guid pguid); + [DllImport(Interop.Libraries.Ole32)] + internal extern static int CoCreateGuid(out Guid guid); } } 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 b86412112..a0c6c0cdc 100644 --- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems @@ -487,7 +487,6 @@ <Compile Include="$(MSBuildThisFileDirectory)System\Single.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Span.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Span.Fast.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" /> @@ -495,7 +494,6 @@ <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" /> <Compile Include="$(MSBuildThisFileDirectory)System\StackOverflowException.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\StringComparer.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\StringComparison.cs" /> @@ -551,6 +549,7 @@ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\TaskToApm.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\TaskSchedulerException.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\ValueTask.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\Sources\IValueTaskSource.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadAbortException.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPriority.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadStart.cs" /> @@ -702,6 +701,7 @@ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Normaliz\Interop.Idna.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Normaliz\Interop.Normalization.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.TimeZone.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Ole32\Interop.CoCreateGuid.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\OleAut32\Interop.SysAllocStringLen.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\OleAut32\Interop.SysFreeString.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\OleAut32\Interop.SysStringLen.cs" /> @@ -717,6 +717,7 @@ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JapaneseCalendar.WinRT.cs" Condition="'$(EnableWinRT)' == 'true'" /> <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\Normalization.Windows.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" /> <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TextInfo.Windows.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" /> + <Compile Include="$(MSBuildThisFileDirectory)System\Guid.Windows.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.Windows.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStreamCompletionSource.Win32.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\IO\Path.Windows.cs" /> @@ -796,6 +797,7 @@ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\LocaleData.Unix.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" /> <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\Normalization.Unix.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" /> <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TextInfo.Unix.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" /> + <Compile Include="$(MSBuildThisFileDirectory)System\Guid.Unix.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.OSX.cs" Condition="'$(TargetsOSX)' == 'true'" /> <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.Linux.cs" Condition="'$(TargetsOSX)' != 'true'" /> <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.Unix.cs" /> diff --git a/src/System.Private.CoreLib/shared/System/Boolean.cs b/src/System.Private.CoreLib/shared/System/Boolean.cs index fd56082f9..e476ef7ce 100644 --- a/src/System.Private.CoreLib/shared/System/Boolean.cs +++ b/src/System.Private.CoreLib/shared/System/Boolean.cs @@ -12,7 +12,6 @@ ** ===========================================================*/ -using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.Versioning; @@ -203,14 +202,14 @@ namespace System public static bool TryParse(ReadOnlySpan<char> value, out bool result) { ReadOnlySpan<char> trueSpan = TrueLiteral.AsSpan(); - if (StringSpanHelpers.Equals(trueSpan, value, StringComparison.OrdinalIgnoreCase)) + if (trueSpan.EqualsOrdinalIgnoreCase(value)) { result = true; return true; } ReadOnlySpan<char> falseSpan = FalseLiteral.AsSpan(); - if (StringSpanHelpers.Equals(falseSpan, value, StringComparison.OrdinalIgnoreCase)) + if (falseSpan.EqualsOrdinalIgnoreCase(value)) { result = false; return true; @@ -219,13 +218,13 @@ namespace System // Special case: Trim whitespace as well as null characters. value = TrimWhiteSpaceAndNull(value); - if (StringSpanHelpers.Equals(trueSpan, value, StringComparison.OrdinalIgnoreCase)) + if (trueSpan.EqualsOrdinalIgnoreCase(value)) { result = true; return true; } - if (StringSpanHelpers.Equals(falseSpan, value, StringComparison.OrdinalIgnoreCase)) + if (falseSpan.EqualsOrdinalIgnoreCase(value)) { result = false; return true; diff --git a/src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs b/src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs index 64c5cebe8..f6affca0d 100644 --- a/src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs +++ b/src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs @@ -6,6 +6,8 @@ using Microsoft.Win32; using System.Runtime.CompilerServices; using System.Threading; +using Internal.Runtime.Augments; + namespace System.Buffers { /// <summary> @@ -228,7 +230,7 @@ namespace System.Buffers // Try to push on to the associated stack first. If that fails, // round-robin through the other stacks. LockedStack[] stacks = _perCoreStacks; - int index = Environment.CurrentExecutionId % stacks.Length; + int index = RuntimeThread.GetCurrentProcessorId() % stacks.Length; for (int i = 0; i < stacks.Length; i++) { if (stacks[index].TryPush(array)) return; @@ -244,7 +246,7 @@ namespace System.Buffers // round-robin through the other stacks. T[] arr; LockedStack[] stacks = _perCoreStacks; - int index = Environment.CurrentExecutionId % stacks.Length; + int index = RuntimeThread.GetCurrentProcessorId() % stacks.Length; for (int i = 0; i < stacks.Length; i++) { if ((arr = stacks[index].TryPop()) != null) return arr; diff --git a/src/System.Private.CoreLib/shared/System/Byte.cs b/src/System.Private.CoreLib/shared/System/Byte.cs index 13ceb7573..31185f0ed 100644 --- a/src/System.Private.CoreLib/shared/System/Byte.cs +++ b/src/System.Private.CoreLib/shared/System/Byte.cs @@ -192,10 +192,6 @@ namespace System return Number.FormatInt32(m_value, format, provider); } - // TODO https://github.com/dotnet/corefx/issues/25337: Remove this overload once corefx is updated to target the new signatures - public bool TryFormat(Span<char> destination, out int charsWritten, string format, IFormatProvider provider) => - TryFormat(destination, out charsWritten, (ReadOnlySpan<char>)format, provider); - public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null) { return Number.TryFormatInt32(m_value, format, provider, destination, out charsWritten); diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs index 1b89158a9..77d305533 100644 --- a/src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs +++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs @@ -72,10 +72,14 @@ namespace System.Collections.Generic { if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity); if (capacity > 0) Initialize(capacity); - _comparer = comparer ?? EqualityComparer<TKey>.Default; + if (comparer != EqualityComparer<TKey>.Default) + { + _comparer = comparer; + } - if (_comparer == EqualityComparer<string>.Default) + if (typeof(TKey) == typeof(string) && _comparer == null) { + // To start, move off default comparer for string which is randomised _comparer = (IEqualityComparer<TKey>)NonRandomizedStringEqualityComparer.Default; } } @@ -143,7 +147,7 @@ namespace System.Collections.Generic { get { - return (_comparer is NonRandomizedStringEqualityComparer) ? (IEqualityComparer<TKey>)EqualityComparer<string>.Default : _comparer; + return (_comparer == null || _comparer is NonRandomizedStringEqualityComparer) ? EqualityComparer<TKey>.Default : _comparer; } } @@ -259,11 +263,7 @@ namespace System.Collections.Generic int count = _count; if (count > 0) { - int[] buckets = _buckets; - for (int i = 0; i < buckets.Length; i++) - { - buckets[i] = -1; - } + Array.Clear(_buckets, 0, _buckets.Length); _count = 0; _freeList = -1; @@ -289,10 +289,9 @@ namespace System.Collections.Generic } else { - EqualityComparer<TValue> c = EqualityComparer<TValue>.Default; for (int i = 0; i < _count; i++) { - if (_entries[i].hashCode >= 0 && c.Equals(_entries[i].value, value)) return true; + if (_entries[i].hashCode >= 0 && EqualityComparer<TValue>.Default.Equals(_entries[i].value, value)) return true; } } return false; @@ -344,7 +343,7 @@ namespace System.Collections.Generic } info.AddValue(VersionName, _version); - info.AddValue(ComparerName, _comparer, typeof(IEqualityComparer<TKey>)); + info.AddValue(ComparerName, _comparer ?? EqualityComparer<TKey>.Default, typeof(IEqualityComparer<TKey>)); info.AddValue(HashSizeName, _buckets == null ? 0 : _buckets.Length); // This is the length of the bucket array if (_buckets != null) @@ -362,28 +361,58 @@ namespace System.Collections.Generic ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } - if (_buckets != null) + int i = -1; + int[] buckets = _buckets; + Entry[] entries = _entries; + if (buckets != null) { - int hashCode = _comparer.GetHashCode(key) & 0x7FFFFFFF; - for (int i = _buckets[hashCode % _buckets.Length]; i >= 0; i = _entries[i].next) + IEqualityComparer<TKey> comparer = _comparer; + if (comparer == null) { - if (_entries[i].hashCode == hashCode && _comparer.Equals(_entries[i].key, key)) return i; + int hashCode = key.GetHashCode() & 0x7FFFFFFF; + // Value in _buckets is 1-based + i = buckets[hashCode % buckets.Length] - 1; + do + { + // Should be a while loop https://github.com/dotnet/coreclr/issues/15476 + // Test in if to drop range check for following array access + if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key))) + { + break; + } + + i = entries[i].next; + } while (true); + } + else + { + int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; + // Value in _buckets is 1-based + i = buckets[hashCode % buckets.Length] - 1; + do + { + // Should be a while loop https://github.com/dotnet/coreclr/issues/15476 + // Test in if to drop range check for following array access + if ((uint)i >= (uint)entries.Length || + (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))) + { + break; + } + + i = entries[i].next; + } while (true); } } - return -1; + + return i; } private int Initialize(int capacity) { int size = HashHelpers.GetPrime(capacity); - int[] buckets = new int[size]; - for (int i = 0; i < buckets.Length; i++) - { - buckets[i] = -1; - } _freeList = -1; - _buckets = buckets; + _buckets = new int[size]; _entries = new Entry[size]; return size; @@ -396,64 +425,134 @@ namespace System.Collections.Generic ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } - if (_buckets == null) Initialize(0); - int hashCode = _comparer.GetHashCode(key) & 0x7FFFFFFF; - int targetBucket = hashCode % _buckets.Length; + if (_buckets == null) + { + Initialize(0); + } + + Entry[] entries = _entries; + IEqualityComparer<TKey> comparer = _comparer; + + int hashCode = ((comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)) & 0x7FFFFFFF; + int collisionCount = 0; + ref int bucket = ref _buckets[hashCode % _buckets.Length]; + // Value in _buckets is 1-based + int i = bucket - 1; - for (int i = _buckets[targetBucket]; i >= 0; i = _entries[i].next) + if (comparer == null) { - if (_entries[i].hashCode == hashCode && _comparer.Equals(_entries[i].key, key)) + do { - if (behavior == InsertionBehavior.OverwriteExisting) + // Should be a while loop https://github.com/dotnet/coreclr/issues/15476 + // Test uint in if rather than loop condition to drop range check for following array access + if ((uint)i >= (uint)entries.Length) { - _entries[i].value = value; - _version++; - return true; + break; } - if (behavior == InsertionBehavior.ThrowOnExisting) + if (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key)) { - ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); + if (behavior == InsertionBehavior.OverwriteExisting) + { + entries[i].value = value; + _version++; + return true; + } + + if (behavior == InsertionBehavior.ThrowOnExisting) + { + ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); + } + + return false; } - return false; - } - collisionCount++; + i = entries[i].next; + collisionCount++; + } while (true); + } + else + { + do + { + // Should be a while loop https://github.com/dotnet/coreclr/issues/15476 + // Test uint in if rather than loop condition to drop range check for following array access + if ((uint)i >= (uint)entries.Length) + { + break; + } + + if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) + { + if (behavior == InsertionBehavior.OverwriteExisting) + { + entries[i].value = value; + _version++; + return true; + } + + if (behavior == InsertionBehavior.ThrowOnExisting) + { + ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); + } + + return false; + } + + i = entries[i].next; + collisionCount++; + } while (true); + } + // Can be improved with "Ref Local Reassignment" + // https://github.com/dotnet/csharplang/blob/master/proposals/ref-local-reassignment.md + bool resized = false; + bool updateFreeList = false; int index; if (_freeCount > 0) { index = _freeList; - _freeList = _entries[index].next; + updateFreeList = true; _freeCount--; } else { - if (_count == _entries.Length) + int count = _count; + if (count == entries.Length) { Resize(); - targetBucket = hashCode % _buckets.Length; + resized = true; } - index = _count; - _count++; + index = count; + _count = count + 1; + entries = _entries; } - _entries[index].hashCode = hashCode; - _entries[index].next = _buckets[targetBucket]; - _entries[index].key = key; - _entries[index].value = value; - _buckets[targetBucket] = index; - _version++; + ref int targetBucket = ref resized ? ref _buckets[hashCode % _buckets.Length] : ref bucket; + ref Entry entry = ref entries[index]; - // If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing - // i.e. EqualityComparer<string>.Default. + if (updateFreeList) + { + _freeList = entry.next; + } + entry.hashCode = hashCode; + // Value in _buckets is 1-based + entry.next = targetBucket - 1; + entry.key = key; + entry.value = value; + // Value in _buckets is 1-based + targetBucket = index + 1; + _version++; - if (collisionCount > HashHelpers.HashCollisionThreshold && _comparer is NonRandomizedStringEqualityComparer) + // Value types never rehash + if (default(TKey) == null && collisionCount > HashHelpers.HashCollisionThreshold && comparer is NonRandomizedStringEqualityComparer) { - _comparer = (IEqualityComparer<TKey>)EqualityComparer<string>.Default; - Resize(_entries.Length, true); + // If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing + // i.e. EqualityComparer<string>.Default. + _comparer = null; + Resize(entries.Length, true); } return true; @@ -512,25 +611,24 @@ namespace System.Collections.Generic private void Resize(int newSize, bool forceNewHashCodes) { + // Value types never rehash + Debug.Assert(!forceNewHashCodes || default(TKey) == null); Debug.Assert(newSize >= _entries.Length); int[] buckets = new int[newSize]; - for (int i = 0; i < buckets.Length; i++) - { - buckets[i] = -1; - } Entry[] entries = new Entry[newSize]; int count = _count; Array.Copy(_entries, 0, entries, 0, count); - if (forceNewHashCodes) + if (default(TKey) == null && forceNewHashCodes) { for (int i = 0; i < count; i++) { - if (entries[i].hashCode != -1) + if (entries[i].hashCode >= 0) { - entries[i].hashCode = (_comparer.GetHashCode(entries[i].key) & 0x7FFFFFFF); + Debug.Assert(_comparer == null); + entries[i].hashCode = (entries[i].key.GetHashCode() & 0x7FFFFFFF); } } } @@ -540,8 +638,10 @@ namespace System.Collections.Generic if (entries[i].hashCode >= 0) { int bucket = entries[i].hashCode % newSize; - entries[i].next = buckets[bucket]; - buckets[bucket] = i; + // Value in _buckets is 1-based + entries[i].next = buckets[bucket] - 1; + // Value in _buckets is 1-based + buckets[bucket] = i + 1; } } @@ -561,19 +661,21 @@ namespace System.Collections.Generic if (_buckets != null) { - int hashCode = _comparer.GetHashCode(key) & 0x7FFFFFFF; + int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF; int bucket = hashCode % _buckets.Length; int last = -1; - int i = _buckets[bucket]; + // Value in _buckets is 1-based + int i = _buckets[bucket] - 1; while (i >= 0) { ref Entry entry = ref _entries[i]; - if (entry.hashCode == hashCode && _comparer.Equals(entry.key, key)) + if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key))) { if (last < 0) { - _buckets[bucket] = entry.next; + // Value in _buckets is 1-based + _buckets[bucket] = entry.next + 1; } else { @@ -615,19 +717,21 @@ namespace System.Collections.Generic if (_buckets != null) { - int hashCode = _comparer.GetHashCode(key) & 0x7FFFFFFF; + int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF; int bucket = hashCode % _buckets.Length; int last = -1; - int i = _buckets[bucket]; + // Value in _buckets is 1-based + int i = _buckets[bucket] - 1; while (i >= 0) { ref Entry entry = ref _entries[i]; - if (entry.hashCode == hashCode && _comparer.Equals(entry.key, key)) + if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key))) { if (last < 0) { - _buckets[bucket] = entry.next; + // Value in _buckets is 1-based + _buckets[bucket] = entry.next + 1; } else { @@ -824,8 +928,10 @@ namespace System.Collections.Generic ref Entry entry = ref entries[count]; entry = oldEntries[i]; int bucket = hashCode % newSize; - entry.next = buckets[bucket]; - buckets[bucket] = count; + // Value in _buckets is 1-based + entry.next = buckets[bucket] - 1; + // Value in _buckets is 1-based + buckets[bucket] = count + 1; count++; } } diff --git a/src/System.Private.CoreLib/shared/System/DateTime.cs b/src/System.Private.CoreLib/shared/System/DateTime.cs index b5deefa94..d3116ee25 100644 --- a/src/System.Private.CoreLib/shared/System/DateTime.cs +++ b/src/System.Private.CoreLib/shared/System/DateTime.cs @@ -1141,13 +1141,6 @@ namespace System return (DateTimeParse.ParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style)); } - // TODO https://github.com/dotnet/corefx/issues/25337: Remove this overload once corefx is updated to target the new signatures - public static DateTime ParseExact(ReadOnlySpan<char> s, string format, IFormatProvider provider, DateTimeStyles style) - { - if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format); - return ParseExact(s, (ReadOnlySpan<char>)format, provider, style); - } - public static DateTime ParseExact(ReadOnlySpan<char> s, ReadOnlySpan<char> format, IFormatProvider provider, DateTimeStyles style = DateTimeStyles.None) { DateTimeFormatInfo.ValidateStyles(style, nameof(style)); @@ -1300,10 +1293,6 @@ namespace System return DateTimeFormat.Format(this, format, DateTimeFormatInfo.GetInstance(provider)); } - // TODO https://github.com/dotnet/corefx/issues/25337: Remove this overload once corefx is updated to target the new signatures - public bool TryFormat(Span<char> destination, out int charsWritten, string format, IFormatProvider provider) => - TryFormat(destination, out charsWritten, (ReadOnlySpan<char>)format, provider); - public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null) => DateTimeFormat.TryFormat(this, destination, out charsWritten, format, DateTimeFormatInfo.GetInstance(provider)); @@ -1359,18 +1348,6 @@ namespace System return DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, out result); } - // TODO https://github.com/dotnet/corefx/issues/25337: Remove this overload once corefx is updated to target the new signatures - public static bool TryParseExact(ReadOnlySpan<char> s, string format, IFormatProvider provider, DateTimeStyles style, out DateTime result) - { - if (format == null) - { - result = default; - return false; - } - - return TryParseExact(s, (ReadOnlySpan<char>)format, provider, style, out result); - } - public static bool TryParseExact(ReadOnlySpan<char> s, ReadOnlySpan<char> format, IFormatProvider provider, DateTimeStyles style, out DateTime result) { DateTimeFormatInfo.ValidateStyles(style, nameof(style)); diff --git a/src/System.Private.CoreLib/shared/System/DateTimeOffset.cs b/src/System.Private.CoreLib/shared/System/DateTimeOffset.cs index bb2196348..1498f9365 100644 --- a/src/System.Private.CoreLib/shared/System/DateTimeOffset.cs +++ b/src/System.Private.CoreLib/shared/System/DateTimeOffset.cs @@ -669,13 +669,6 @@ namespace System return new DateTimeOffset(dateResult.Ticks, offset); } - // TODO https://github.com/dotnet/corefx/issues/25337: Remove this overload once corefx is updated to target the new signatures - public static DateTimeOffset ParseExact(ReadOnlySpan<char> input, string format, IFormatProvider formatProvider, DateTimeStyles styles) - { - if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format); - return ParseExact(input, (ReadOnlySpan<char>)format, formatProvider, styles); - } - public static DateTimeOffset ParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, DateTimeStyles styles = DateTimeStyles.None) { styles = ValidateStyles(styles, nameof(styles)); @@ -780,10 +773,6 @@ namespace System return DateTimeFormat.Format(ClockDateTime, format, DateTimeFormatInfo.GetInstance(formatProvider), Offset); } - // TODO https://github.com/dotnet/corefx/issues/25337: Remove this overload once corefx is updated to target the new signatures - public bool TryFormat(Span<char> destination, out int charsWritten, string format, IFormatProvider provider) => - TryFormat(destination, out charsWritten, (ReadOnlySpan<char>)format, provider); - public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider formatProvider = null) => DateTimeFormat.TryFormat(ClockDateTime, destination, out charsWritten, format, DateTimeFormatInfo.GetInstance(formatProvider), Offset); @@ -862,18 +851,6 @@ namespace System return parsed; } - // TODO https://github.com/dotnet/corefx/issues/25337: Remove this overload once corefx is updated to target the new signatures - public static bool TryParseExact(ReadOnlySpan<char> input, string format, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result) - { - if (format == null) - { - result = default; - return false; - } - - return TryParseExact(input, (ReadOnlySpan<char>)format, formatProvider, styles, out result); - } - public static bool TryParseExact( ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result) { diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs index 1444c267c..11c18a260 100644 --- a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs +++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs @@ -143,6 +143,45 @@ namespace System.Diagnostics.Tracing } } + internal unsafe void AddNullTerminatedString(string value) + { + // Treat null strings as empty strings. + if (value == null) + { + value = string.Empty; + } + + // Calculate the size of the string including the trailing NULL char. + // Don't use value.Length here because string allows for embedded NULL characters. + int nullCharIndex = value.IndexOf((char)0); + if (nullCharIndex < 0) + { + nullCharIndex = value.Length; + } + int size = (nullCharIndex + 1) * 2; + + if (this.bufferNesting != 0) + { + this.EnsureBuffer(size); + } + + if (this.bufferNesting == 0) + { + this.ScalarsEnd(); + this.PinArray(value, size); + } + else + { + var oldPos = this.bufferPos; + this.bufferPos = checked(this.bufferPos + size); + this.EnsureBuffer(); + fixed (void* p = value) + { + Marshal.Copy((IntPtr)p, buffer, oldPos, size); + } + } + } + internal void AddBinary(Array value, int size) { this.AddArray(value, size, 1); diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs index 9c7c6369e..f15373475 100644 --- a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs +++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs @@ -135,13 +135,11 @@ namespace System.Diagnostics.Tracing { throw new NotSupportedException(SR.EventSource_NotSupportedArrayOfBinary); } -#if !BROKEN_UNTIL_M3 if (coreType == (int)TraceLoggingDataType.Utf16String || coreType == (int)TraceLoggingDataType.MbcsString) { throw new NotSupportedException(SR.EventSource_NotSupportedArrayOfNullTerminatedString); } -#endif } if (((int)this.tags & 0xfffffff) != 0) diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs index 9b58d8251..2f460d24c 100644 --- a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs +++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs @@ -151,12 +151,12 @@ namespace System.Diagnostics.Tracing string name, EventFieldFormat format) { - collector.AddBinary(name, Statics.MakeDataType(TraceLoggingDataType.CountedUtf16String, format)); + collector.AddNullTerminatedString(name, Statics.MakeDataType(TraceLoggingDataType.Utf16String, format)); } public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value) { - collector.AddBinary((string)value.ReferenceValue); + collector.AddNullTerminatedString((string)value.ReferenceValue); } public override object GetData(object value) diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs index 04a047fb3..f6d0a59aa 100644 --- a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs +++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs @@ -85,6 +85,17 @@ namespace System.Diagnostics.Tracing } /// <summary> + /// Adds a null-terminated String value to the event payload. + /// </summary> + /// <param name="value"> + /// Value to be added. A null value is treated as a zero-length string. + /// </param> + public void AddNullTerminatedString(string value) + { + DataCollector.ThreadInstance.AddNullTerminatedString(value); + } + + /// <summary> /// Adds a counted String value to the event payload. /// </summary> /// <param name="value"> diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs index 4348df7d6..10b378c2b 100644 --- a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs +++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs @@ -557,30 +557,14 @@ namespace System.Diagnostics.Tracing for (int i = 0; i < eventTypes.typeInfos.Length; i++) { - // Until M3, we need to morph strings to a counted representation - // When TDH supports null terminated strings, we can remove this. - if (eventTypes.typeInfos[i].DataType == typeof(string)) - { - // Write out the size of the string - descriptors[numDescrs].DataPointer = (IntPtr) (&descriptors[numDescrs + 1].m_Size); - descriptors[numDescrs].m_Size = 2; - numDescrs++; - - descriptors[numDescrs].m_Ptr = data[i].m_Ptr; - descriptors[numDescrs].m_Size = data[i].m_Size - 2; // Remove the null terminator - numDescrs++; - } - else - { - descriptors[numDescrs].m_Ptr = data[i].m_Ptr; - descriptors[numDescrs].m_Size = data[i].m_Size; + descriptors[numDescrs].m_Ptr = data[i].m_Ptr; + descriptors[numDescrs].m_Size = data[i].m_Size; - // old conventions for bool is 4 bytes, but meta-data assumes 1. - if (data[i].m_Size == 4 && eventTypes.typeInfos[i].DataType == typeof(bool)) - descriptors[numDescrs].m_Size = 1; + // old conventions for bool is 4 bytes, but meta-data assumes 1. + if (data[i].m_Size == 4 && eventTypes.typeInfos[i].DataType == typeof(bool)) + descriptors[numDescrs].m_Size = 1; - numDescrs++; - } + numDescrs++; } this.WriteEventRaw( diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs index 1db1a28c9..b5b199dbc 100644 --- a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs +++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs @@ -192,22 +192,43 @@ namespace System.Diagnostics.Tracing } /// <summary> + /// Adds a null-terminated string field to an event. + /// Compatible with core types: Utf16String, MbcsString. + /// Compatible with dataCollector method: AddNullTerminatedString(string). + /// </summary> + /// <param name="name"> + /// The name to use for the added field. This value must not be null. + /// </param> + /// <param name="type"> + /// The type code for the added field. This must be a null-terminated string type. + /// </param> + public void AddNullTerminatedString(string name, TraceLoggingDataType type) + { + switch ((TraceLoggingDataType)((int)type & Statics.InTypeMask)) + { + case TraceLoggingDataType.Utf16String: + break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + + this.impl.AddNonscalar(); + this.AddField(new FieldMetadata(name, type, this.Tags, this.BeginningBufferedArray)); + } + + /// <summary> /// Adds an array field to an event. /// </summary> /// <param name="name"> /// The name to use for the added field. This value must not be null. /// </param> /// <param name="type"> - /// The type code for the added field. This must be a fixed-size type - /// or a string type. In the case of a string type, this adds an array - /// of characters, not an array of strings. + /// The type code for the added field. This must be a fixed-size type. /// </param> public void AddArray(string name, TraceLoggingDataType type) { switch ((TraceLoggingDataType)((int)type & Statics.InTypeMask)) { - case TraceLoggingDataType.Utf16String: - case TraceLoggingDataType.MbcsString: case TraceLoggingDataType.Int8: case TraceLoggingDataType.UInt8: case TraceLoggingDataType.Int16: diff --git a/src/System.Private.CoreLib/shared/System/Double.cs b/src/System.Private.CoreLib/shared/System/Double.cs index 146ee4600..1351ae91d 100644 --- a/src/System.Private.CoreLib/shared/System/Double.cs +++ b/src/System.Private.CoreLib/shared/System/Double.cs @@ -346,15 +346,15 @@ namespace System if (!success) { ReadOnlySpan<char> sTrim = s.Trim(); - if (StringSpanHelpers.Equals(sTrim, info.PositiveInfinitySymbol)) + if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol)) { result = PositiveInfinity; } - else if (StringSpanHelpers.Equals(sTrim, info.NegativeInfinitySymbol)) + else if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol)) { result = NegativeInfinity; } - else if (StringSpanHelpers.Equals(sTrim, info.NaNSymbol)) + else if (sTrim.EqualsOrdinal(info.NaNSymbol)) { result = NaN; } diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Unix.cs index 926eaa13d..17d6ed7a0 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Unix.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Unix.cs @@ -54,6 +54,13 @@ namespace System.Globalization Debug.Assert(calendarId == CalendarId.HEBREW && saMonthNames.Length == 13); saLeapYearMonthNames = (string[]) saMonthNames.Clone(); saLeapYearMonthNames[6] = leapHebrewMonthName; + + // The returned data from ICU has 6th month name as 'Adar I' and 7th month name as 'Adar' + // We need to adjust that in the list used with non-leap year to have 6th month as 'Adar' and 7th month as 'Adar II' + // note that when formatting non-leap year dates, 7th month shouldn't get used at all. + saMonthNames[5] = saMonthNames[6]; + saMonthNames[6] = leapHebrewMonthName; + } result &= EnumMonthNames(localeName, calendarId, CalendarDataType.AbbrevMonthNames, out this.saAbbrevMonthNames, ref leapHebrewMonthName); result &= EnumMonthNames(localeName, calendarId, CalendarDataType.MonthGenitiveNames, out this.saMonthGenitiveNames, ref leapHebrewMonthName); diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs index c369c816b..db0a660dd 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs @@ -389,38 +389,18 @@ namespace System.Globalization return CompareString(string1, string2, options); } - // TODO https://github.com/dotnet/corefx/issues/21395: Expose this publicly? - internal virtual int Compare(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options) + internal virtual int CompareOptionNone(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2) { - if (options == CompareOptions.OrdinalIgnoreCase) - { - return CompareOrdinalIgnoreCase(string1, string2); - } - - // Verify the options before we do any real comparison. - if ((options & CompareOptions.Ordinal) != 0) - { - if (options != CompareOptions.Ordinal) - { - throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options)); - } - - return string.CompareOrdinal(string1, string2); - } - - if ((options & ValidCompareMaskOffFlags) != 0) - { - throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); - } - - if (_invariantMode) - { - return (options & CompareOptions.IgnoreCase) != 0 ? - CompareOrdinalIgnoreCase(string1, string2) : - string.CompareOrdinal(string1, string2); - } + return _invariantMode ? + string.CompareOrdinal(string1, string2) : + CompareString(string1, string2, CompareOptions.None); + } - return CompareString(string1, string2, options); + internal virtual int CompareOptionIgnoreCase(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2) + { + return _invariantMode ? + CompareOrdinalIgnoreCase(string1, string2) : + CompareString(string1, string2, CompareOptions.IgnoreCase); } //////////////////////////////////////////////////////////////////////// diff --git a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfo.cs index faa0deac3..edec75ac8 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfo.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfo.cs @@ -1602,7 +1602,7 @@ namespace System.Globalization result = this.AllYearMonthPatterns; break; default: - throw new ArgumentException(SR.Format_BadFormatSpecifier, nameof(format)); + throw new ArgumentException(SR.Format(SR.Format_BadFormatSpecifier, format), nameof(format)); } return (result); } @@ -1950,7 +1950,7 @@ namespace System.Globalization break; default: - throw new ArgumentException(SR.Format_BadFormatSpecifier, nameof(format)); + throw new ArgumentException(SR.Format(SR.Format_BadFormatSpecifier, format), nameof(format)); } // Clear the token hash table, note that even short dates could require this diff --git a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeParse.cs b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeParse.cs index 5b285eb5d..fd64c6d11 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.AsSpan().Slice(targetPosition, segmentLength), CompareOptions.IgnoreCase) != 0) + if (m_info.CompareOptionIgnoreCase(Value.Slice(thisPosition, segmentLength), target.AsSpan().Slice(targetPosition, segmentLength)) != 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.AsSpan().Slice(targetPosition, segmentLength), CompareOptions.IgnoreCase) != 0) + if (m_info.CompareOptionIgnoreCase(Value.Slice(thisPosition, segmentLength), target.AsSpan().Slice(targetPosition, segmentLength)) != 0) { return false; } @@ -5286,14 +5286,17 @@ new DS[] { DS.ERROR, DS.TX_NNN, DS.TX_NNN, DS.TX_NNN, DS.ERROR, DS.ERROR, // Check if the last character is a quote. if (ch == '\'' || ch == '\"') { - if (Char.IsWhiteSpace(Value[i - 1])) + if (char.IsWhiteSpace(Value[i - 1])) { i--; - while (i >= 1 && Char.IsWhiteSpace(Value[i - 1])) + while (i >= 1 && char.IsWhiteSpace(Value[i - 1])) { i--; } - Value = Value.Remove(i, Value.Length - 1 - i); + Span<char> result = new char[i + 1]; + result[i] = ch; + Value.Slice(0, i).CopyTo(result); + Value = result.AsReadOnlySpan(); } } } @@ -5311,13 +5314,16 @@ new DS[] { DS.ERROR, DS.TX_NNN, DS.TX_NNN, DS.TX_NNN, DS.ERROR, DS.ERROR, // Check if the last character is a quote. if (ch == '\'' || ch == '\"') { - while ((i + 1) < Length && Char.IsWhiteSpace(Value[i + 1])) + while ((i + 1) < Length && char.IsWhiteSpace(Value[i + 1])) { i++; } if (i != 0) { - Value = Value.Remove(1, i); + Span<char> result = new char[Value.Length - i]; + result[0] = ch; + Value.Slice(i + 1).CopyTo(result.Slice(1)); + Value = result.AsReadOnlySpan(); } } } diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanParse.cs b/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanParse.cs index ae77957ce..51fac39d0 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanParse.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanParse.cs @@ -274,83 +274,83 @@ namespace System.Globalization internal bool FullAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) => _sepCount == 5 && _numCount == 4 - && StringSpanHelpers.Equals(_literals0, pattern.Start) - && StringSpanHelpers.Equals(_literals1, pattern.DayHourSep) - && StringSpanHelpers.Equals(_literals2, pattern.HourMinuteSep) - && StringSpanHelpers.Equals(_literals3, pattern.AppCompatLiteral) - && StringSpanHelpers.Equals(_literals4, pattern.End); + && _literals0.EqualsOrdinal(pattern.Start) + && _literals1.EqualsOrdinal(pattern.DayHourSep) + && _literals2.EqualsOrdinal(pattern.HourMinuteSep) + && _literals3.EqualsOrdinal(pattern.AppCompatLiteral) + && _literals4.EqualsOrdinal(pattern.End); internal bool PartialAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) => _sepCount == 4 && _numCount == 3 - && StringSpanHelpers.Equals(_literals0, pattern.Start) - && StringSpanHelpers.Equals(_literals1, pattern.HourMinuteSep) - && StringSpanHelpers.Equals(_literals2, pattern.AppCompatLiteral) - && StringSpanHelpers.Equals(_literals3, pattern.End); + && _literals0.EqualsOrdinal(pattern.Start) + && _literals1.EqualsOrdinal(pattern.HourMinuteSep) + && _literals2.EqualsOrdinal(pattern.AppCompatLiteral) + && _literals3.EqualsOrdinal(pattern.End); /// <summary>DHMSF (all values matched)</summary> internal bool FullMatch(TimeSpanFormat.FormatLiterals pattern) => _sepCount == MaxLiteralTokens && _numCount == MaxNumericTokens - && StringSpanHelpers.Equals(_literals0, pattern.Start) - && StringSpanHelpers.Equals(_literals1, pattern.DayHourSep) - && StringSpanHelpers.Equals(_literals2, pattern.HourMinuteSep) - && StringSpanHelpers.Equals(_literals3, pattern.MinuteSecondSep) - && StringSpanHelpers.Equals(_literals4, pattern.SecondFractionSep) - && StringSpanHelpers.Equals(_literals5, pattern.End); + && _literals0.EqualsOrdinal(pattern.Start) + && _literals1.EqualsOrdinal(pattern.DayHourSep) + && _literals2.EqualsOrdinal(pattern.HourMinuteSep) + && _literals3.EqualsOrdinal(pattern.MinuteSecondSep) + && _literals4.EqualsOrdinal(pattern.SecondFractionSep) + && _literals5.EqualsOrdinal(pattern.End); /// <summary>D (no hours, minutes, seconds, or fractions)</summary> internal bool FullDMatch(TimeSpanFormat.FormatLiterals pattern) => _sepCount == 2 && _numCount == 1 - && StringSpanHelpers.Equals(_literals0, pattern.Start) - && StringSpanHelpers.Equals(_literals1, pattern.End); + && _literals0.EqualsOrdinal(pattern.Start) + && _literals1.EqualsOrdinal(pattern.End); /// <summary>HM (no days, seconds, or fractions)</summary> internal bool FullHMMatch(TimeSpanFormat.FormatLiterals pattern) => _sepCount == 3 && _numCount == 2 - && StringSpanHelpers.Equals(_literals0, pattern.Start) - && StringSpanHelpers.Equals(_literals1, pattern.HourMinuteSep) - && StringSpanHelpers.Equals(_literals2, pattern.End); + && _literals0.EqualsOrdinal(pattern.Start) + && _literals1.EqualsOrdinal(pattern.HourMinuteSep) + && _literals2.EqualsOrdinal(pattern.End); /// <summary>DHM (no seconds or fraction)</summary> internal bool FullDHMMatch(TimeSpanFormat.FormatLiterals pattern) => _sepCount == 4 && _numCount == 3 - && StringSpanHelpers.Equals(_literals0, pattern.Start) - && StringSpanHelpers.Equals(_literals1, pattern.DayHourSep) - && StringSpanHelpers.Equals(_literals2, pattern.HourMinuteSep) - && StringSpanHelpers.Equals(_literals3, pattern.End); + && _literals0.EqualsOrdinal(pattern.Start) + && _literals1.EqualsOrdinal(pattern.DayHourSep) + && _literals2.EqualsOrdinal(pattern.HourMinuteSep) + && _literals3.EqualsOrdinal(pattern.End); /// <summary>HMS (no days or fraction)</summary> internal bool FullHMSMatch(TimeSpanFormat.FormatLiterals pattern) => _sepCount == 4 && _numCount == 3 - && StringSpanHelpers.Equals(_literals0, pattern.Start) - && StringSpanHelpers.Equals(_literals1, pattern.HourMinuteSep) - && StringSpanHelpers.Equals(_literals2, pattern.MinuteSecondSep) - && StringSpanHelpers.Equals(_literals3, pattern.End); + && _literals0.EqualsOrdinal(pattern.Start) + && _literals1.EqualsOrdinal(pattern.HourMinuteSep) + && _literals2.EqualsOrdinal(pattern.MinuteSecondSep) + && _literals3.EqualsOrdinal(pattern.End); /// <summary>DHMS (no fraction)</summary> internal bool FullDHMSMatch(TimeSpanFormat.FormatLiterals pattern) => _sepCount == 5 && _numCount == 4 - && StringSpanHelpers.Equals(_literals0, pattern.Start) - && StringSpanHelpers.Equals(_literals1, pattern.DayHourSep) - && StringSpanHelpers.Equals(_literals2, pattern.HourMinuteSep) - && StringSpanHelpers.Equals(_literals3, pattern.MinuteSecondSep) - && StringSpanHelpers.Equals(_literals4, pattern.End); + && _literals0.EqualsOrdinal(pattern.Start) + && _literals1.EqualsOrdinal(pattern.DayHourSep) + && _literals2.EqualsOrdinal(pattern.HourMinuteSep) + && _literals3.EqualsOrdinal(pattern.MinuteSecondSep) + && _literals4.EqualsOrdinal(pattern.End); /// <summary>HMSF (no days)</summary> internal bool FullHMSFMatch(TimeSpanFormat.FormatLiterals pattern) => _sepCount == 5 && _numCount == 4 - && StringSpanHelpers.Equals(_literals0, pattern.Start) - && StringSpanHelpers.Equals(_literals1, pattern.HourMinuteSep) - && StringSpanHelpers.Equals(_literals2, pattern.MinuteSecondSep) - && StringSpanHelpers.Equals(_literals3, pattern.SecondFractionSep) - && StringSpanHelpers.Equals(_literals4, pattern.End); + && _literals0.EqualsOrdinal(pattern.Start) + && _literals1.EqualsOrdinal(pattern.HourMinuteSep) + && _literals2.EqualsOrdinal(pattern.MinuteSecondSep) + && _literals3.EqualsOrdinal(pattern.SecondFractionSep) + && _literals4.EqualsOrdinal(pattern.End); internal TTT _lastSeenTTT; internal int _tokenCount; diff --git a/src/System.Private.CoreLib/shared/System/Guid.Unix.cs b/src/System.Private.CoreLib/shared/System/Guid.Unix.cs new file mode 100644 index 000000000..442e7f883 --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/Guid.Unix.cs @@ -0,0 +1,38 @@ +// 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 +{ + partial struct Guid + { + // This will create a new random guid based on the https://www.ietf.org/rfc/rfc4122.txt + public static unsafe Guid NewGuid() + { + Guid g; + Interop.GetRandomBytes((byte*)&g, sizeof(Guid)); + + const ushort VersionMask = 0xF000; + const ushort RandomGuidVersion = 0x4000; + + const byte ClockSeqHiAndReservedMask = 0xC0; + const byte ClockSeqHiAndReservedValue = 0x80; + + // Modify bits indicating the type of the GUID + + unchecked + { + // time_hi_and_version + g._c = (short)((g._c & ~VersionMask) | RandomGuidVersion); + // clock_seq_hi_and_reserved + g._d = (byte)((g._d & ~ClockSeqHiAndReservedMask) | ClockSeqHiAndReservedValue); + } + + return g; + } + } +} + diff --git a/src/System.Private.CoreLib/src/System/Guid.Windows.cs b/src/System.Private.CoreLib/shared/System/Guid.Windows.cs index f21dfbd6b..f00fbe45b 100644 --- a/src/System.Private.CoreLib/src/System/Guid.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/Guid.Windows.cs @@ -6,16 +6,13 @@ namespace System { partial struct Guid { - // This will create a new guid. Since we've now decided that constructors should 0-init, - // we need a method that allows users to create a guid. public static Guid NewGuid() { // CoCreateGuid should never return Guid.Empty, since it attempts to maintain some - // uniqueness guarantees. It should also never return a known GUID, but it's unclear - // how extensively it checks for known values. + // uniqueness guarantees. Guid g; - int hr = Interop.mincore.CoCreateGuid(out g); + int hr = Interop.Ole32.CoCreateGuid(out g); // We don't expect that this will ever throw an error, none are even documented, and so we don't want to pull // in the HR to ComException mappings into the core library just for this so we will try a generic exception if // we ever hit this condition. @@ -29,3 +26,4 @@ namespace System } } } + diff --git a/src/System.Private.CoreLib/shared/System/Guid.cs b/src/System.Private.CoreLib/shared/System/Guid.cs index 423d5bc78..1d0942f2c 100644 --- a/src/System.Private.CoreLib/shared/System/Guid.cs +++ b/src/System.Private.CoreLib/shared/System/Guid.cs @@ -449,7 +449,7 @@ namespace System } // Check for braces - bool bracesExistInString = (guidString.IndexOf('{', 0) >= 0); + bool bracesExistInString = (guidString.IndexOf('{') >= 0); if (bracesExistInString) { @@ -471,7 +471,7 @@ namespace System } // Check for parenthesis - bool parenthesisExistInString = (guidString.IndexOf('(', 0) >= 0); + bool parenthesisExistInString = (guidString.IndexOf('(') >= 0); if (parenthesisExistInString) { @@ -548,7 +548,7 @@ namespace System // Find the end of this hex number (since it is not fixed length) numStart = 3; - numLen = guidString.IndexOf(',', numStart) - numStart; + numLen = guidString.Slice(numStart).IndexOf(','); if (numLen <= 0) { result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidComma)); @@ -566,7 +566,7 @@ namespace System } // +3 to get by ',0x' numStart = numStart + numLen + 3; - numLen = guidString.IndexOf(',', numStart) - numStart; + numLen = guidString.Slice(numStart).IndexOf(','); if (numLen <= 0) { result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidComma)); @@ -584,7 +584,7 @@ namespace System } // +3 to get by ',0x' numStart = numStart + numLen + 3; - numLen = guidString.IndexOf(',', numStart) - numStart; + numLen = guidString.Slice(numStart).IndexOf(','); if (numLen <= 0) { result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidComma)); @@ -621,7 +621,7 @@ namespace System // Calculate number length if (i < 7) // first 7 cases { - numLen = guidString.IndexOf(',', numStart) - numStart; + numLen = guidString.Slice(numStart).IndexOf(','); if (numLen <= 0) { result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidComma)); @@ -630,7 +630,7 @@ namespace System } else // last case ends with '}', not ',' { - numLen = guidString.IndexOf('}', numStart) - numStart; + numLen = guidString.Slice(numStart).IndexOf('}'); if (numLen <= 0) { result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidBraceAfterLastNumber)); diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs b/src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs index 31b9ac53b..d9fcf6571 100644 --- a/src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs +++ b/src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs @@ -635,12 +635,12 @@ namespace System.IO /// <param name="source">The buffer to write data from.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <returns>A task that represents the asynchronous write operation.</returns> - private Task WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) + private ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) { Debug.Assert(_useAsyncIO); if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); + return new ValueTask(Task.FromCanceled(cancellationToken)); if (_fileHandle.IsClosed) throw Error.GetFileNotOpen(); @@ -667,11 +667,11 @@ namespace System.IO source.Span.CopyTo(new Span<byte>(GetBuffer(), _writePos, source.Length)); _writePos += source.Length; - return Task.CompletedTask; + return default; } catch (Exception exc) { - return Task.FromException(exc); + return new ValueTask(Task.FromException(exc)); } finally { @@ -682,7 +682,7 @@ namespace System.IO // Otherwise, issue the whole request asynchronously. _asyncState.ReadOnlyMemory = source; - return waitTask.ContinueWith((t, s) => + return new ValueTask(waitTask.ContinueWith((t, s) => { // The options available on Unix for writing asynchronously to an arbitrary file // handle typically amount to just using another thread to do the synchronous write, @@ -702,7 +702,7 @@ namespace System.IO thisRef.WriteSpan(readOnlyMemory.Span); } finally { thisRef._asyncState.Release(); } - }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); + }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default)); } /// <summary>Sets the current position of this stream to the given value.</summary> diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs b/src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs index 85f045426..291a30bb5 100644 --- a/src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs @@ -961,7 +961,7 @@ namespace System.IO return completionSource.Task; } - private Task WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) + private ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) { Debug.Assert(_useAsyncIO); Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both."); @@ -1005,7 +1005,7 @@ namespace System.IO // completely, we want to do the asynchronous flush/write as part of this operation // rather than waiting until the next write that fills the buffer. if (source.Length != remainingBuffer) - return Task.CompletedTask; + return default; Debug.Assert(_writePos == _bufferLength); } @@ -1051,7 +1051,7 @@ namespace System.IO flushTask.IsFaulted || flushTask.IsCanceled) { - return flushTask; + return new ValueTask(flushTask); } } @@ -1061,10 +1061,10 @@ namespace System.IO // Finally, issue the write asynchronously, and return a Task that logically // represents the write operation, including any flushing done. Task writeTask = WriteAsyncInternalCore(source, cancellationToken); - return + return new ValueTask( (flushTask == null || flushTask.Status == TaskStatus.RanToCompletion) ? writeTask : (writeTask.Status == TaskStatus.RanToCompletion) ? flushTask : - Task.WhenAll(flushTask, writeTask); + Task.WhenAll(flushTask, writeTask)); } private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) @@ -1319,7 +1319,7 @@ namespace System.IO int bufferedBytes = _readLength - _readPos; if (bufferedBytes > 0) { - await destination.WriteAsync(GetBuffer(), _readPos, bufferedBytes, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(new ReadOnlyMemory<byte>(GetBuffer(), _readPos, bufferedBytes), cancellationToken).ConfigureAwait(false); _readPos = _readLength = 0; } } @@ -1345,7 +1345,6 @@ namespace System.IO // Further, typically the CopyToAsync buffer size will be larger than that used by the FileStream, such that // we'd likely be unable to use it anyway. Instead, we rent the buffer from a pool. byte[] copyBuffer = ArrayPool<byte>.Shared.Rent(bufferSize); - bufferSize = 0; // repurpose bufferSize to be the high water mark for the buffer, to avoid an extra field in the state machine // Allocate an Overlapped we can use repeatedly for all operations var awaitableOverlapped = new PreAllocatedOverlapped(AsyncCopyToAwaitable.s_callback, readAwaitable, copyBuffer); @@ -1452,13 +1451,6 @@ namespace System.IO { readAwaitable._position += numBytesRead; } - - // (and keep track of the maximum number of bytes in the buffer we used, to avoid excessive and unnecessary - // clearing of the buffer before we return it to the pool) - if (numBytesRead > bufferSize) - { - bufferSize = numBytesRead; - } } finally { @@ -1479,7 +1471,7 @@ namespace System.IO } // Write out the read data. - await destination.WriteAsync(copyBuffer, 0, (int)readAwaitable._numBytes, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(new ReadOnlyMemory<byte>(copyBuffer, 0, (int)readAwaitable._numBytes), cancellationToken).ConfigureAwait(false); } } finally @@ -1488,8 +1480,7 @@ namespace System.IO cancellationReg.Dispose(); awaitableOverlapped.Dispose(); - Array.Clear(copyBuffer, 0, bufferSize); - ArrayPool<byte>.Shared.Return(copyBuffer, clearArray: false); + ArrayPool<byte>.Shared.Return(copyBuffer); // Make sure the stream's current position reflects where we ended up if (!_fileHandle.IsClosed && CanSeek) diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStream.cs b/src/System.Private.CoreLib/shared/System/IO/FileStream.cs index 4593768bd..717b73ff1 100644 --- a/src/System.Private.CoreLib/shared/System/IO/FileStream.cs +++ b/src/System.Private.CoreLib/shared/System/IO/FileStream.cs @@ -458,10 +458,10 @@ namespace System.IO if (IsClosed) throw Error.GetFileNotOpen(); - return WriteAsyncInternal(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken); + return WriteAsyncInternal(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask(); } - public override Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) + public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) { if (!_useAsyncIO || GetType() != typeof(FileStream)) { @@ -473,7 +473,7 @@ namespace System.IO if (cancellationToken.IsCancellationRequested) { - return Task.FromCanceled<int>(cancellationToken); + return new ValueTask(Task.FromCanceled<int>(cancellationToken)); } if (IsClosed) @@ -853,7 +853,7 @@ namespace System.IO if (!IsAsync) return base.BeginWrite(array, offset, numBytes, callback, state); else - return TaskToApm.Begin(WriteAsyncInternal(new ReadOnlyMemory<byte>(array, offset, numBytes), CancellationToken.None), callback, state); + return TaskToApm.Begin(WriteAsyncInternal(new ReadOnlyMemory<byte>(array, offset, numBytes), CancellationToken.None).AsTask(), callback, state); } public override int EndRead(IAsyncResult asyncResult) diff --git a/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs b/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs index c5e5ea918..ffe7f6093 100644 --- a/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs +++ b/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs @@ -448,7 +448,7 @@ namespace System.IO // something other than an array and this is a MemoryStream-derived type that doesn't override Read(Span<byte>) will // it then fall back to doing the ArrayPool/copy behavior. return new ValueTask<int>( - destination.TryGetArray(out ArraySegment<byte> destinationArray) ? + MemoryMarshal.TryGetArray(destination, out ArraySegment<byte> destinationArray) ? Read(destinationArray.Array, destinationArray.Offset, destinationArray.Count) : Read(destination.Span)); } @@ -752,11 +752,11 @@ namespace System.IO } } - public override Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) + public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { - return Task.FromCanceled(cancellationToken); + return new ValueTask(Task.FromCanceled(cancellationToken)); } try @@ -771,15 +771,15 @@ namespace System.IO { Write(source.Span); } - return Task.CompletedTask; + return default; } catch (OperationCanceledException oce) { - return Task.FromCancellation<VoidTaskResult>(oce); + return new ValueTask(Task.FromCancellation<VoidTaskResult>(oce)); } catch (Exception exception) { - return Task.FromException(exception); + return new ValueTask(Task.FromException(exception)); } } 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 b921db9e6..2f1a527d8 100644 --- a/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs @@ -72,6 +72,9 @@ namespace System.IO if (IsPathFullyQualified(path)) return GetFullPath(path); + if (PathInternal.IsEffectivelyEmpty(path)) + return basePath; + int length = path.Length; string combinedPath = null; @@ -80,14 +83,14 @@ 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 = Join(GetPathRoot(basePath.AsSpan()), path); + combinedPath = Join(GetPathRoot(basePath.AsSpan()), path.AsSpan().Slice(1)); // Cut the separator to ensure we don't end up with two separators when joining with the root. } 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), GetVolumeName(basePath))) + if (GetVolumeName(path).EqualsOrdinal(GetVolumeName(basePath))) { // Matching root // "C:Foo" and "C:\Bar" => "C:\Bar\Foo" @@ -118,7 +121,8 @@ namespace System.IO // to GetFullPath() won't do anything by design. Additionally, GetFullPathName() in // Windows doesn't root them properly. As such we need to manually remove segments. return PathInternal.IsDevice(combinedPath) - ? RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath)) + // Paths at this point are in the form of \\?\C:\.\tmp we skip to the last character of the root when calling RemoveRelativeSegments to remove relative paths in such cases. + ? RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath) - 1) : GetFullPath(combinedPath); } @@ -237,11 +241,11 @@ namespace System.IO { bool isDevice = PathInternal.IsDevice(path); - if (!isDevice && StringSpanHelpers.Equals(path.Slice(0, 2), @"\\") ) + if (!isDevice && path.Slice(0, 2).EqualsOrdinal(@"\\") ) return 2; else if (isDevice && path.Length >= 8 - && (StringSpanHelpers.Equals(path.Slice(0, 8), PathInternal.UncExtendedPathPrefix) - || StringSpanHelpers.Equals(path.Slice(5, 4), @"UNC\"))) + && (path.Slice(0, 8).EqualsOrdinal(PathInternal.UncExtendedPathPrefix) + || path.Slice(5, 4).EqualsOrdinal(@"UNC\"))) return 8; return -1; diff --git a/src/System.Private.CoreLib/shared/System/IO/Path.cs b/src/System.Private.CoreLib/shared/System/IO/Path.cs index 41ae1cd0b..fe6234cc7 100644 --- a/src/System.Private.CoreLib/shared/System/IO/Path.cs +++ b/src/System.Private.CoreLib/shared/System/IO/Path.cs @@ -738,7 +738,7 @@ namespace System.IO { if (PathInternal.IsDirectorySeparator(sb[s])) { - sb.Length = s; + sb.Length = (i + 3 >= path.Length && s <= skip ) ? s + 1 : s; // to avoid removing the complete "\tmp\" segment in cases like \\?\C:\tmp\..\, C:\tmp\.. break; } } 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 039a28834..6faebe8ff 100644 --- a/src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs @@ -35,7 +35,7 @@ namespace System.IO // TryExpandShortName does this input identity check. string result = builder.AsSpan().Contains('~') ? TryExpandShortFileName(ref builder, originalPath: path) - : builder.AsSpan().Equals(path.AsReadOnlySpan()) ? path : builder.ToString(); + : builder.AsSpan().EqualsOrdinal(path.AsSpan()) ? path : builder.ToString(); // Clear the buffer builder.Dispose(); @@ -220,7 +220,7 @@ namespace System.IO // Strip out any added characters at the front of the string ReadOnlySpan<char> output = builderToUse.AsSpan().Slice(rootDifference); - string returnValue = output.Equals(originalPath.AsReadOnlySpan()) + string returnValue = output.EqualsOrdinal(originalPath.AsSpan()) ? originalPath : new string(output); inputBuilder.Dispose(); 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 0e31731a6..fae309be5 100644 --- a/src/System.Private.CoreLib/shared/System/IO/PathInternal.Unix.cs +++ b/src/System.Private.CoreLib/shared/System/IO/PathInternal.Unix.cs @@ -84,11 +84,6 @@ namespace System.IO return !Path.IsPathRooted(path); } - internal static string TrimEndingDirectorySeparator(string path) => - path.Length > 1 && IsDirectorySeparator(path[path.Length - 1]) ? // exclude root "/" - path.Substring(0, path.Length - 1) : - path; - /// <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 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 2ef666959..81d51ba4b 100644 --- a/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs @@ -70,7 +70,36 @@ namespace System.IO return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z')); } + private static bool EndsWithPeriodOrSpace(string path) + { + if (string.IsNullOrEmpty(path)) + return false; + + char c = path[path.Length - 1]; + return c == ' ' || c == '.'; + } + + /// <summary> + /// Adds the extended path prefix (\\?\) if not already a device path, IF the path is not relative, + /// AND the path is more than 259 characters. (> MAX_PATH + null). This will also insert the extended + /// prefix if the path ends with a period or a space. Trailing periods and spaces are normally eaten + /// away from paths during normalization, but if we see such a path at this point it should be + /// normalized and has retained the final characters. (Typically from one of the *Info classes) + /// </summary> + internal static string EnsureExtendedPrefixIfNeeded(string path) + { + if (path != null && (path.Length >= MaxShortPath || EndsWithPeriodOrSpace(path))) + { + return EnsureExtendedPrefix(path); + } + else + { + return path; + } + } + /// <summary> + /// DO NOT USE- Use EnsureExtendedPrefixIfNeeded. This will be removed shortly. /// Adds the extended path prefix (\\?\) if not already a device path, IF the path is not relative, /// AND the path is more than 259 characters. (> MAX_PATH + null) /// </summary> diff --git a/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs b/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs index f2f350ddd..eb06c2608 100644 --- a/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs +++ b/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs @@ -10,13 +10,30 @@ namespace System.IO /// <summary> /// Returns true if the path ends in a directory separator. /// </summary> - internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) => path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]); + internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) + => path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]); /// <summary> /// Returns true if the path starts in a directory separator. /// </summary> internal static bool StartsWithDirectorySeparator(ReadOnlySpan<char> path) => path.Length > 0 && IsDirectorySeparator(path[0]); + internal static string EnsureTrailingSeparator(string path) + => EndsInDirectorySeparator(path) ? path : path + DirectorySeparatorCharAsString; + + internal static string TrimEndingDirectorySeparator(string path) => + EndsInDirectorySeparator(path) && !IsRoot(path) ? + path.Substring(0, path.Length - 1) : + path; + + internal static ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path) => + EndsInDirectorySeparator(path) && !IsRoot(path) ? + path.Slice(0, path.Length - 1) : + path; + + internal static bool IsRoot(ReadOnlySpan<char> path) + => path.Length == GetRootLength(path); + /// <summary> /// Get the common path length from the start of the string. /// </summary> diff --git a/src/System.Private.CoreLib/shared/System/IO/StreamReader.cs b/src/System.Private.CoreLib/shared/System/IO/StreamReader.cs index 4e724ddb3..22ec6e645 100644 --- a/src/System.Private.CoreLib/shared/System/IO/StreamReader.cs +++ b/src/System.Private.CoreLib/shared/System/IO/StreamReader.cs @@ -1091,7 +1091,7 @@ namespace System.IO { Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?"); int tmpBytePos = _bytePos; - int len = await tmpStream.ReadAsync(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos, cancellationToken).ConfigureAwait(false); + int len = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos), cancellationToken).ConfigureAwait(false); Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); if (len == 0) @@ -1127,7 +1127,7 @@ namespace System.IO { Debug.Assert(_bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?"); - _byteLen = await tmpStream.ReadAsync(tmpByteBuffer, 0, tmpByteBuffer.Length, cancellationToken).ConfigureAwait(false); + _byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer), cancellationToken).ConfigureAwait(false); Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); @@ -1303,7 +1303,7 @@ namespace System.IO { Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?"); int tmpBytePos = _bytePos; - int len = await tmpStream.ReadAsync(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos).ConfigureAwait(false); + int len = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos)).ConfigureAwait(false); Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); if (len == 0) @@ -1325,7 +1325,7 @@ namespace System.IO else { Debug.Assert(_bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?"); - _byteLen = await tmpStream.ReadAsync(tmpByteBuffer, 0, tmpByteBuffer.Length).ConfigureAwait(false); + _byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer)).ConfigureAwait(false); Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! Bug in stream class."); if (_byteLen == 0) // We're at EOF diff --git a/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs b/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs index 6cdcd6952..a37624428 100644 --- a/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs +++ b/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs @@ -963,14 +963,14 @@ namespace System.IO byte[] preamble = encoding.GetPreamble(); if (preamble.Length > 0) { - await stream.WriteAsync(preamble, 0, preamble.Length, cancellationToken).ConfigureAwait(false); + await stream.WriteAsync(new ReadOnlyMemory<byte>(preamble), cancellationToken).ConfigureAwait(false); } } int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder); if (count > 0) { - await stream.WriteAsync(byteBuffer, 0, count, cancellationToken).ConfigureAwait(false); + await stream.WriteAsync(new ReadOnlyMemory<byte>(byteBuffer, 0, count), cancellationToken).ConfigureAwait(false); } // By definition, calling Flush should flush the stream, but this is diff --git a/src/System.Private.CoreLib/shared/System/IO/TextReader.cs b/src/System.Private.CoreLib/shared/System/IO/TextReader.cs index c4727cd84..eb94dd759 100644 --- a/src/System.Private.CoreLib/shared/System/IO/TextReader.cs +++ b/src/System.Private.CoreLib/shared/System/IO/TextReader.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Buffers; namespace System.IO @@ -251,7 +252,7 @@ namespace System.IO } public virtual ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) => - new ValueTask<int>(buffer.TryGetArray(out ArraySegment<char> array) ? + new ValueTask<int>(MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ? ReadAsync(array.Array, array.Offset, array.Count) : Task<int>.Factory.StartNew(state => { @@ -289,7 +290,7 @@ namespace System.IO } public virtual ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) => - new ValueTask<int>(buffer.TryGetArray(out ArraySegment<char> array) ? + new ValueTask<int>(MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ? ReadBlockAsync(array.Array, array.Offset, array.Count) : Task<int>.Factory.StartNew(state => { diff --git a/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs index 171113542..d1a13156a 100644 --- a/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs +++ b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs @@ -510,7 +510,7 @@ namespace System.IO // something other than an array and this is an UnmanagedMemoryStream-derived type that doesn't override Read(Span<byte>) will // it then fall back to doing the ArrayPool/copy behavior. return new ValueTask<int>( - destination.TryGetArray(out ArraySegment<byte> destinationArray) ? + MemoryMarshal.TryGetArray(destination, out ArraySegment<byte> destinationArray) ? Read(destinationArray.Array, destinationArray.Offset, destinationArray.Count) : Read(destination.Span)); } @@ -783,11 +783,11 @@ namespace System.IO /// </summary> /// <param name="buffer">Buffer that will be written.</param> /// <param name="cancellationToken">Token that can be used to cancel the operation.</param> - public override Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) + public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { - return Task.FromCanceled(cancellationToken); + return new ValueTask(Task.FromCanceled(cancellationToken)); } try @@ -802,11 +802,11 @@ namespace System.IO { Write(source.Span); } - return Task.CompletedTask; + return default; } catch (Exception ex) { - return Task.FromException(ex); + return new ValueTask(Task.FromException(ex)); } } diff --git a/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs index 90bb21ac5..f34c3c413 100644 --- a/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs +++ b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs @@ -217,7 +217,7 @@ namespace System.IO return _unmanagedStream.WriteAsync(buffer, offset, count, cancellationToken); } - public override Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) + public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) { return _unmanagedStream.WriteAsync(source, cancellationToken); } diff --git a/src/System.Private.CoreLib/shared/System/Memory.cs b/src/System.Private.CoreLib/shared/System/Memory.cs index 4a1ce4dc9..82c66d6ea 100644 --- a/src/System.Private.CoreLib/shared/System/Memory.cs +++ b/src/System.Private.CoreLib/shared/System/Memory.cs @@ -18,8 +18,8 @@ namespace System /// Memory represents a contiguous region of arbitrary memory similar to <see cref="Span{T}"/>. /// Unlike <see cref="Span{T}"/>, it is not a byref-like type. /// </summary> - [DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerTypeProxy(typeof(MemoryDebugView<>))] + [DebuggerDisplay("{ToString(),raw}")] public readonly struct Memory<T> { // NOTE: With the current implementation, Memory<T> and ReadOnlyMemory<T> must have the same layout, @@ -145,9 +145,6 @@ namespace System public static implicit operator ReadOnlyMemory<T>(Memory<T> memory) => Unsafe.As<Memory<T>, ReadOnlyMemory<T>>(ref memory); - //Debugger Display = {T[length]} - private string DebuggerDisplay => string.Format("{{{0}[{1}]}}", typeof(T).Name, _length); - /// <summary> /// Returns an empty <see cref="Memory{T}"/> /// </summary> @@ -164,6 +161,19 @@ namespace System public bool IsEmpty => _length == 0; /// <summary> + /// For <see cref="Memory{Char}"/>, returns a new instance of string that represents the characters pointed to by the memory. + /// Otherwise, returns a <see cref="string"/> with the name of the type and the number of elements. + /// </summary> + public override string ToString() + { + if (typeof(T) == typeof(char)) + { + return (_object is string str) ? str.Substring(_index, _length) : Span.ToString(); + } + return string.Format("System.Memory<{0}>[{1}]", typeof(T).Name, _length); + } + + /// <summary> /// Forms a slice out of the given memory, beginning at 'start'. /// </summary> /// <param name="start">The index at which to begin this slice.</param> @@ -310,40 +320,6 @@ namespace System } /// <summary> - /// Get an array segment from the underlying memory. - /// If unable to get the array segment, return false with a default array segment. - /// </summary> - public bool TryGetArray(out ArraySegment<T> arraySegment) - { - if (_index < 0) - { - if (((OwnedMemory<T>)_object).TryGetArray(out var segment)) - { - arraySegment = new ArraySegment<T>(segment.Array, segment.Offset + (_index & RemoveOwnedFlagBitMask), _length); - return true; - } - } - else if (_object is T[] arr) - { - arraySegment = new ArraySegment<T>(arr, _index, _length); - 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; - } - - /// <summary> /// Copies the contents from the memory into a new array. This heap /// allocates, so should generally be avoided, however it is sometimes /// necessary to bridge the gap with APIs written in terms of arrays. diff --git a/src/System.Private.CoreLib/shared/System/MemoryDebugView.cs b/src/System.Private.CoreLib/shared/System/MemoryDebugView.cs index fa508b286..f56a67c63 100644 --- a/src/System.Private.CoreLib/shared/System/MemoryDebugView.cs +++ b/src/System.Private.CoreLib/shared/System/MemoryDebugView.cs @@ -22,31 +22,6 @@ namespace System } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public T[] Items - { - // This is a work around since we cannot use _memory.ToArray() due to - // https://devdiv.visualstudio.com/DevDiv/_workitems?id=286592 - get - { - if (MemoryMarshal.TryGetArray(_memory, out ArraySegment<T> segment)) - { - T[] array = new T[_memory.Length]; - Array.Copy(segment.Array, segment.Offset, array, 0, array.Length); - return array; - } - - if (typeof(T) == typeof(char) && - ((ReadOnlyMemory<char>)(object)_memory).TryGetString(out string text, out int start, out int length)) - { - return (T[])(object)text.Substring(start, length).ToCharArray(); - } - -#if FEATURE_PORTABLE_SPAN - return SpanHelpers.PerTypeValues<T>.EmptyArray; -#else - return Array.Empty<T>(); -#endif // FEATURE_PORTABLE_SPAN - } - } + public T[] Items => _memory.ToArray(); } } diff --git a/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs b/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs index 4b330b7b1..d28c3d688 100644 --- a/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs +++ b/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs @@ -36,41 +36,67 @@ namespace System /// </summary> public static bool Equals(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType) { - StringSpanHelpers.CheckStringComparison(comparisonType); + string.CheckStringComparison(comparisonType); switch (comparisonType) { case StringComparison.CurrentCulture: - return (CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.None) == 0); + return (CultureInfo.CurrentCulture.CompareInfo.CompareOptionNone(span, value) == 0); case StringComparison.CurrentCultureIgnoreCase: - return (CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.IgnoreCase) == 0); + return (CultureInfo.CurrentCulture.CompareInfo.CompareOptionIgnoreCase(span, value) == 0); case StringComparison.InvariantCulture: - return (CompareInfo.Invariant.Compare(span, value, CompareOptions.None) == 0); + return (CompareInfo.Invariant.CompareOptionNone(span, value) == 0); case StringComparison.InvariantCultureIgnoreCase: - return (CompareInfo.Invariant.Compare(span, value, CompareOptions.IgnoreCase) == 0); + return (CompareInfo.Invariant.CompareOptionIgnoreCase(span, value) == 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 + return EqualsOrdinal(span, value); 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); + return EqualsOrdinalIgnoreCase(span, value); } Debug.Fail("StringComparison outside range"); return false; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool EqualsOrdinal(this ReadOnlySpan<char> span, ReadOnlySpan<char> value) + { + 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 + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool EqualsOrdinalIgnoreCase(this ReadOnlySpan<char> span, ReadOnlySpan<char> value) + { + if (span.Length != value.Length) + return false; + if (value.Length == 0) // span.Length == value.Length == 0 + return true; + return (CompareInfo.CompareOrdinalIgnoreCase(span, value) == 0); + } + + // TODO https://github.com/dotnet/corefx/issues/27526 + internal static bool Contains(this ReadOnlySpan<char> source, char value) + { + for (int i = 0; i < source.Length; i++) + { + if (source[i] == value) + { + return true; + } + } + + 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. @@ -80,21 +106,21 @@ namespace System /// </summary> public static int CompareTo(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType) { - StringSpanHelpers.CheckStringComparison(comparisonType); + string.CheckStringComparison(comparisonType); switch (comparisonType) { case StringComparison.CurrentCulture: - return CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.None); + return CultureInfo.CurrentCulture.CompareInfo.CompareOptionNone(span, value); case StringComparison.CurrentCultureIgnoreCase: - return CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.IgnoreCase); + return CultureInfo.CurrentCulture.CompareInfo.CompareOptionIgnoreCase(span, value); case StringComparison.InvariantCulture: - return CompareInfo.Invariant.Compare(span, value, CompareOptions.None); + return CompareInfo.Invariant.CompareOptionNone(span, value); case StringComparison.InvariantCultureIgnoreCase: - return CompareInfo.Invariant.Compare(span, value, CompareOptions.IgnoreCase); + return CompareInfo.Invariant.CompareOptionIgnoreCase(span, value); case StringComparison.Ordinal: if (span.Length == 0 || value.Length == 0) @@ -117,7 +143,7 @@ namespace System /// </summary> public static int IndexOf(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType) { - StringSpanHelpers.CheckStringComparison(comparisonType); + string.CheckStringComparison(comparisonType); if (value.Length == 0) { @@ -262,7 +288,7 @@ namespace System { if (value.Length == 0) { - StringSpanHelpers.CheckStringComparison(comparisonType); + string.CheckStringComparison(comparisonType); return true; } @@ -301,7 +327,7 @@ namespace System { if (value.Length == 0) { - StringSpanHelpers.CheckStringComparison(comparisonType); + string.CheckStringComparison(comparisonType); return true; } @@ -512,29 +538,5 @@ namespace System 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 index effdecf92..d5a6b72eb 100644 --- a/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs +++ b/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs @@ -181,7 +181,7 @@ namespace System 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); + return SpanHelpers.IndexOf(ref MemoryMarshal.GetReference(span), value, span.Length); } /// <summary> @@ -199,7 +199,7 @@ namespace System 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); + return SpanHelpers.IndexOf(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length); } /// <summary> @@ -301,7 +301,7 @@ namespace System 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); + return SpanHelpers.IndexOf(ref MemoryMarshal.GetReference(span), value, span.Length); } /// <summary> @@ -319,7 +319,7 @@ namespace System 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); + return SpanHelpers.IndexOf(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length); } /// <summary> @@ -707,7 +707,7 @@ namespace System } /// <summary> - /// Creates a new span over the portion of the target array. + /// Creates a new span over the target array. /// </summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span<T> AsSpan<T>(this T[] array) @@ -716,12 +716,73 @@ namespace System } /// <summary> + /// Creates a new Span over the portion of the target array beginning + /// at 'start' index and ending at 'end' index (exclusive). + /// </summary> + /// <param name="array">The target array.</param> + /// <param name="start">The index at which to begin the Span.</param> + /// <param name="length">The number of items in the Span.</param> + /// <remarks>Returns default when <paramref name="array"/> is null.</remarks> + /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception> + /// <exception cref="System.ArgumentOutOfRangeException"> + /// Thrown when the specified <paramref name="start"/> or end index is not in the range (<0 or >=Length). + /// </exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span<T> AsSpan<T>(this T[] array, int start, int length) + { + return new Span<T>(array, start, length); + } + + /// <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) + public static Span<T> AsSpan<T>(this ArraySegment<T> segment) { - return new Span<T>(arraySegment.Array, arraySegment.Offset, arraySegment.Count); + return new Span<T>(segment.Array, segment.Offset, segment.Count); + } + + /// <summary> + /// Creates a new Span over the portion of the target array beginning + /// at 'start' index and ending at 'end' index (exclusive). + /// </summary> + /// <param name="segment">The target array.</param> + /// <param name="start">The index at which to begin the Span.</param> + /// <remarks>Returns default when <paramref name="segment"/> is null.</remarks> + /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="segment"/> is covariant and array's type is not exactly T[].</exception> + /// <exception cref="System.ArgumentOutOfRangeException"> + /// Thrown when the specified <paramref name="start"/> or end index is not in the range (<0 or >=segment.Count). + /// </exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span<T> AsSpan<T>(this ArraySegment<T> segment, int start) + { + if (((uint)start) > segment.Count) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + + return new Span<T>(segment.Array, segment.Offset + start, segment.Count - start); + } + + /// <summary> + /// Creates a new Span over the portion of the target array beginning + /// at 'start' index and ending at 'end' index (exclusive). + /// </summary> + /// <param name="segment">The target array.</param> + /// <param name="start">The index at which to begin the Span.</param> + /// <param name="length">The number of items in the Span.</param> + /// <remarks>Returns default when <paramref name="segment"/> is null.</remarks> + /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="segment"/> is covariant and array's type is not exactly T[].</exception> + /// <exception cref="System.ArgumentOutOfRangeException"> + /// Thrown when the specified <paramref name="start"/> or end index is not in the range (<0 or >=segment.Count). + /// </exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span<T> AsSpan<T>(this ArraySegment<T> segment, int start, int length) + { + if (((uint)start) > segment.Count) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + if (((uint)length) > segment.Count - start) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length); + + return new Span<T>(segment.Array, segment.Offset + start, length); } /// <summary> @@ -753,11 +814,84 @@ namespace System public static ReadOnlyMemory<T> AsReadOnlyMemory<T>(this Memory<T> memory) => memory; /// <summary> - /// Creates a new memory over the portion of the target array. + /// Creates a new memory over the target array. + /// </summary> + public static Memory<T> AsMemory<T>(this T[] array) => new Memory<T>(array); + + /// <summary> + /// Creates a new memory over the portion of the target array beginning + /// at 'start' index and ending at 'end' index (exclusive). /// </summary> + /// <param name="array">The target array.</param> + /// <param name="start">The index at which to begin the memory.</param> + /// <remarks>Returns default when <paramref name="array"/> is null.</remarks> + /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception> + /// <exception cref="System.ArgumentOutOfRangeException"> + /// Thrown when the specified <paramref name="start"/> or end index is not in the range (<0 or >=array.Length). + /// </exception> public static Memory<T> AsMemory<T>(this T[] array, int start) => new Memory<T>(array, start); /// <summary> + /// Creates a new memory over the portion of the target array beginning + /// at 'start' index and ending at 'end' index (exclusive). + /// </summary> + /// <param name="array">The target array.</param> + /// <param name="start">The index at which to begin the memory.</param> + /// <param name="length">The number of items in the memory.</param> + /// <remarks>Returns default when <paramref name="array"/> is null.</remarks> + /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception> + /// <exception cref="System.ArgumentOutOfRangeException"> + /// Thrown when the specified <paramref name="start"/> or end index is not in the range (<0 or >=Length). + /// </exception> + public static Memory<T> AsMemory<T>(this T[] array, int start, int length) => new Memory<T>(array, start, length); + + /// <summary> + /// Creates a new memory over the portion of the target array. + /// </summary> + public static Memory<T> AsMemory<T>(this ArraySegment<T> segment) => new Memory<T>(segment.Array, segment.Offset, segment.Count); + + /// <summary> + /// Creates a new memory over the portion of the target array beginning + /// at 'start' index and ending at 'end' index (exclusive). + /// </summary> + /// <param name="segment">The target array.</param> + /// <param name="start">The index at which to begin the memory.</param> + /// <remarks>Returns default when <paramref name="segment"/> is null.</remarks> + /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="segment"/> is covariant and array's type is not exactly T[].</exception> + /// <exception cref="System.ArgumentOutOfRangeException"> + /// Thrown when the specified <paramref name="start"/> or end index is not in the range (<0 or >=segment.Count). + /// </exception> + public static Memory<T> AsMemory<T>(this ArraySegment<T> segment, int start) + { + if (((uint)start) > segment.Count) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + + return new Memory<T>(segment.Array, segment.Offset + start, segment.Count - start); + } + + /// <summary> + /// Creates a new memory over the portion of the target array beginning + /// at 'start' index and ending at 'end' index (exclusive). + /// </summary> + /// <param name="segment">The target array.</param> + /// <param name="start">The index at which to begin the memory.</param> + /// <param name="length">The number of items in the memory.</param> + /// <remarks>Returns default when <paramref name="segment"/> is null.</remarks> + /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="segment"/> is covariant and array's type is not exactly T[].</exception> + /// <exception cref="System.ArgumentOutOfRangeException"> + /// Thrown when the specified <paramref name="start"/> or end index is not in the range (<0 or >=segment.Count). + /// </exception> + public static Memory<T> AsMemory<T>(this ArraySegment<T> segment, int start, int length) + { + if (((uint)start) > segment.Count) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + if (((uint)length) > segment.Count - start) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length); + + return new Memory<T>(segment.Array, segment.Offset + start, length); + } + + /// <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. diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index 46951094e..c6ae34d05 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -734,15 +734,15 @@ namespace System //Check the three with which we're concerned and rethrow if it's not one of //those strings. ReadOnlySpan<char> sTrim = value.Trim(); - if (StringSpanHelpers.Equals(sTrim, numfmt.PositiveInfinitySymbol)) + if (sTrim.EqualsOrdinal(numfmt.PositiveInfinitySymbol)) { return double.PositiveInfinity; } - if (StringSpanHelpers.Equals(sTrim, numfmt.NegativeInfinitySymbol)) + if (sTrim.EqualsOrdinal(numfmt.NegativeInfinitySymbol)) { return double.NegativeInfinity; } - if (StringSpanHelpers.Equals(sTrim, numfmt.NaNSymbol)) + if (sTrim.EqualsOrdinal(numfmt.NaNSymbol)) { return double.NaN; } @@ -768,15 +768,15 @@ namespace System //Check the three with which we're concerned and rethrow if it's not one of //those strings. ReadOnlySpan<char> sTrim = value.Trim(); - if (StringSpanHelpers.Equals(sTrim, numfmt.PositiveInfinitySymbol)) + if (sTrim.EqualsOrdinal(numfmt.PositiveInfinitySymbol)) { return float.PositiveInfinity; } - if (StringSpanHelpers.Equals(sTrim, numfmt.NegativeInfinitySymbol)) + if (sTrim.EqualsOrdinal(numfmt.NegativeInfinitySymbol)) { return float.NegativeInfinity; } - if (StringSpanHelpers.Equals(sTrim, numfmt.NaNSymbol)) + if (sTrim.EqualsOrdinal(numfmt.NaNSymbol)) { return float.NaN; } diff --git a/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs b/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs index 166a204ed..90a9decf6 100644 --- a/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs +++ b/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs @@ -18,8 +18,8 @@ namespace System /// Represents a contiguous region of memory, similar to <see cref="ReadOnlySpan{T}"/>. /// Unlike <see cref="ReadOnlySpan{T}"/>, it is not a byref-like type. /// </summary> - [DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerTypeProxy(typeof(MemoryDebugView<>))] + [DebuggerDisplay("{ToString(),raw}")] public readonly struct ReadOnlyMemory<T> { // NOTE: With the current implementation, Memory<T> and ReadOnlyMemory<T> must have the same layout, @@ -97,9 +97,6 @@ namespace System _length = length; } - //Debugger Display = {T[length]} - private string DebuggerDisplay => string.Format("{{{0}[{1}]}}", typeof(T).Name, _length); - /// <summary> /// Defines an implicit conversion of an array to a <see cref="ReadOnlyMemory{T}"/> /// </summary> @@ -126,6 +123,19 @@ namespace System public bool IsEmpty => _length == 0; /// <summary> + /// For <see cref="ReadOnlyMemory{Char}"/>, returns a new instance of string that represents the characters pointed to by the memory. + /// Otherwise, returns a <see cref="string"/> with the name of the type and the number of elements. + /// </summary> + public override string ToString() + { + if (typeof(T) == typeof(char)) + { + return (_object is string str) ? str.Substring(_index, _length) : Span.ToString(); + } + return string.Format("System.ReadOnlyMemory<{0}>[{1}]", typeof(T).Name, _length); + } + + /// <summary> /// Forms a slice out of the given memory, beginning at 'start'. /// </summary> /// <param name="start">The index at which to begin this slice.</param> diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs index 49cdaccb0..0e1220d11 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs @@ -8,6 +8,108 @@ using System.Threading.Tasks; namespace System.Runtime.CompilerServices { + /// <summary>Represents a builder for asynchronous methods that return a <see cref="ValueTask"/>.</summary> + [StructLayout(LayoutKind.Auto)] + public struct AsyncValueTaskMethodBuilder + { + /// <summary>The <see cref="AsyncTaskMethodBuilder"/> to which most operations are delegated.</summary> + private AsyncTaskMethodBuilder _methodBuilder; // mutable struct; do not make it readonly + /// <summary>true if completed synchronously and successfully; otherwise, false.</summary> + private bool _haveResult; + /// <summary>true if the builder should be used for setting/getting the result; otherwise, false.</summary> + private bool _useBuilder; + + /// <summary>Creates an instance of the <see cref="AsyncValueTaskMethodBuilder"/> struct.</summary> + /// <returns>The initialized instance.</returns> + public static AsyncValueTaskMethodBuilder Create() => +#if CORERT + // corert's AsyncTaskMethodBuilder.Create() currently does additional debugger-related + // work, so we need to delegate to it. + new AsyncValueTaskMethodBuilder() { _methodBuilder = AsyncTaskMethodBuilder.Create() }; +#else + // _methodBuilder should be initialized to AsyncTaskMethodBuilder.Create(), but on coreclr + // that Create() is a nop, so we can just return the default here. + default; +#endif + + /// <summary>Begins running the builder with the associated state machine.</summary> + /// <typeparam name="TStateMachine">The type of the state machine.</typeparam> + /// <param name="stateMachine">The state machine instance, passed by reference.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => + // will provide the right ExecutionContext semantics +#if netstandard + _methodBuilder.Start(ref stateMachine); +#else + AsyncMethodBuilderCore.Start(ref stateMachine); +#endif + + /// <summary>Associates the builder with the specified state machine.</summary> + /// <param name="stateMachine">The state machine instance to associate with the builder.</param> + public void SetStateMachine(IAsyncStateMachine stateMachine) => _methodBuilder.SetStateMachine(stateMachine); + + /// <summary>Marks the task as successfully completed.</summary> + public void SetResult() + { + if (_useBuilder) + { + _methodBuilder.SetResult(); + } + else + { + _haveResult = true; + } + } + + /// <summary>Marks the task as failed and binds the specified exception to the task.</summary> + /// <param name="exception">The exception to bind to the task.</param> + public void SetException(Exception exception) => _methodBuilder.SetException(exception); + + /// <summary>Gets the task for this builder.</summary> + public ValueTask Task + { + get + { + if (_haveResult) + { + return default; + } + else + { + _useBuilder = true; + return new ValueTask(_methodBuilder.Task); + } + } + } + + /// <summary>Schedules the state machine to proceed to the next action when the specified awaiter completes.</summary> + /// <typeparam name="TAwaiter">The type of the awaiter.</typeparam> + /// <typeparam name="TStateMachine">The type of the state machine.</typeparam> + /// <param name="awaiter">The awaiter.</param> + /// <param name="stateMachine">The state machine.</param> + public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine + { + _useBuilder = true; + _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine); + } + + /// <summary>Schedules the state machine to proceed to the next action when the specified awaiter completes.</summary> + /// <typeparam name="TAwaiter">The type of the awaiter.</typeparam> + /// <typeparam name="TStateMachine">The type of the state machine.</typeparam> + /// <param name="awaiter">The awaiter.</param> + /// <param name="stateMachine">The state machine.</param> + [SecuritySafeCritical] + public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + _useBuilder = true; + _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); + } + } + /// <summary>Represents a builder for asynchronous methods that returns a <see cref="ValueTask{TResult}"/>.</summary> /// <typeparam name="TResult">The type of the result.</typeparam> [StructLayout(LayoutKind.Auto)] @@ -32,7 +134,7 @@ namespace System.Runtime.CompilerServices #else // _methodBuilder should be initialized to AsyncTaskMethodBuilder<TResult>.Create(), but on coreclr // that Create() is a nop, so we can just return the default here. - default(AsyncValueTaskMethodBuilder<TResult>); + default; #endif /// <summary>Begins running the builder with the associated state machine.</summary> diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs index f22b9d94b..599453198 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs @@ -5,9 +5,115 @@ using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading.Tasks; +using System.Threading.Tasks.Sources; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif namespace System.Runtime.CompilerServices { + /// <summary>Provides an awaitable type that enables configured awaits on a <see cref="ValueTask"/>.</summary> + [StructLayout(LayoutKind.Auto)] + public readonly struct ConfiguredValueTaskAwaitable + { + /// <summary>The wrapped <see cref="Task"/>.</summary> + private readonly ValueTask _value; + + /// <summary>Initializes the awaitable.</summary> + /// <param name="value">The wrapped <see cref="ValueTask"/>.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredValueTaskAwaitable(ValueTask value) => _value = value; + + /// <summary>Returns an awaiter for this <see cref="ConfiguredValueTaskAwaitable"/> instance.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConfiguredValueTaskAwaiter GetAwaiter() => new ConfiguredValueTaskAwaiter(_value); + + /// <summary>Provides an awaiter for a <see cref="ConfiguredValueTaskAwaitable"/>.</summary> + [StructLayout(LayoutKind.Auto)] + public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion +#if CORECLR + , IValueTaskAwaiter +#endif + { + /// <summary>The value being awaited.</summary> + private readonly ValueTask _value; + + /// <summary>Initializes the awaiter.</summary> + /// <param name="value">The value to be awaited.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredValueTaskAwaiter(ValueTask value) => _value = value; + + /// <summary>Gets whether the <see cref="ConfiguredValueTaskAwaitable"/> has completed.</summary> + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _value.IsCompleted; + } + + /// <summary>Gets the result of the ValueTask.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + public void GetResult() => _value.ThrowIfCompletedUnsuccessfully(); + + /// <summary>Schedules the continuation action for the <see cref="ConfiguredValueTaskAwaitable"/>.</summary> + public void OnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeGetTask().ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().OnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeGetValueTaskSource().OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, + ValueTaskSourceOnCompletedFlags.FlowExecutionContext | + (_value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None)); + } + else + { + ValueTask.CompletedTask.ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().OnCompleted(continuation); + } + } + + /// <summary>Schedules the continuation action for the <see cref="ConfiguredValueTaskAwaitable"/>.</summary> + public void UnsafeOnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeGetTask().ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeGetValueTaskSource().OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, + _value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); + } + else + { + ValueTask.CompletedTask.ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation); + } + } + +#if CORECLR + void IValueTaskAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box) + { + if (_value.ObjectIsTask) + { + TaskAwaiter.UnsafeOnCompletedInternal(_value.UnsafeGetTask(), box, _value.ContinueOnCapturedContext); + } + else if (_value._obj != null) + { + _value.UnsafeGetValueTaskSource().OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, _value._token, + _value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); + } + else + { + TaskAwaiter.UnsafeOnCompletedInternal(Task.CompletedTask, box, _value.ContinueOnCapturedContext); + } + } +#endif + } + } + /// <summary>Provides an awaitable type that enables configured awaits on a <see cref="ValueTask{TResult}"/>.</summary> /// <typeparam name="TResult">The type of the result produced.</typeparam> [StructLayout(LayoutKind.Auto)] @@ -15,78 +121,98 @@ namespace System.Runtime.CompilerServices { /// <summary>The wrapped <see cref="ValueTask{TResult}"/>.</summary> private readonly ValueTask<TResult> _value; - /// <summary>true to attempt to marshal the continuation back to the original context captured; otherwise, false.</summary> - private readonly bool _continueOnCapturedContext; /// <summary>Initializes the awaitable.</summary> /// <param name="value">The wrapped <see cref="ValueTask{TResult}"/>.</param> - /// <param name="continueOnCapturedContext"> - /// true to attempt to marshal the continuation back to the original synchronization context captured; otherwise, false. - /// </param> - internal ConfiguredValueTaskAwaitable(ValueTask<TResult> value, bool continueOnCapturedContext) - { - _value = value; - _continueOnCapturedContext = continueOnCapturedContext; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredValueTaskAwaitable(ValueTask<TResult> value) => _value = value; /// <summary>Returns an awaiter for this <see cref="ConfiguredValueTaskAwaitable{TResult}"/> instance.</summary> - public ConfiguredValueTaskAwaiter GetAwaiter() => - new ConfiguredValueTaskAwaiter(_value, _continueOnCapturedContext); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConfiguredValueTaskAwaiter GetAwaiter() => new ConfiguredValueTaskAwaiter(_value); /// <summary>Provides an awaiter for a <see cref="ConfiguredValueTaskAwaitable{TResult}"/>.</summary> [StructLayout(LayoutKind.Auto)] - public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion, IConfiguredValueTaskAwaiter + public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion +#if CORECLR + , IValueTaskAwaiter +#endif { /// <summary>The value being awaited.</summary> private readonly ValueTask<TResult> _value; - /// <summary>The value to pass to ConfigureAwait.</summary> - internal readonly bool _continueOnCapturedContext; /// <summary>Initializes the awaiter.</summary> /// <param name="value">The value to be awaited.</param> - /// <param name="continueOnCapturedContext">The value to pass to ConfigureAwait.</param> - internal ConfiguredValueTaskAwaiter(ValueTask<TResult> value, bool continueOnCapturedContext) - { - _value = value; - _continueOnCapturedContext = continueOnCapturedContext; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredValueTaskAwaiter(ValueTask<TResult> value) => _value = value; /// <summary>Gets whether the <see cref="ConfiguredValueTaskAwaitable{TResult}"/> has completed.</summary> - public bool IsCompleted => _value.IsCompleted; + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _value.IsCompleted; + } /// <summary>Gets the result of the ValueTask.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] [StackTraceHidden] - public TResult GetResult() => - _value._task == null ? - _value._result : - _value._task.GetAwaiter().GetResult(); + public TResult GetResult() => _value.Result; /// <summary>Schedules the continuation action for the <see cref="ConfiguredValueTaskAwaitable{TResult}"/>.</summary> - public void OnCompleted(Action continuation) => - _value.AsTask().ConfigureAwait(_continueOnCapturedContext).GetAwaiter().OnCompleted(continuation); + public void OnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeGetTask().ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().OnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeGetValueTaskSource().OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, + ValueTaskSourceOnCompletedFlags.FlowExecutionContext | + (_value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None)); + } + else + { + ValueTask.CompletedTask.ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().OnCompleted(continuation); + } + } /// <summary>Schedules the continuation action for the <see cref="ConfiguredValueTaskAwaitable{TResult}"/>.</summary> - public void UnsafeOnCompleted(Action continuation) => - _value.AsTask().ConfigureAwait(_continueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation); - - /// <summary>Gets the task underlying <see cref="_value"/>.</summary> - internal Task<TResult> AsTask() => _value.AsTask(); + public void UnsafeOnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeGetTask().ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeGetValueTaskSource().OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, + _value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); + } + else + { + ValueTask.CompletedTask.ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation); + } + } - /// <summary>Gets the task underlying the incomplete <see cref="_value"/>.</summary> - /// <remarks>This method is used when awaiting and IsCompleted returned false; thus we expect the value task to be wrapping a non-null task.</remarks> - Task IConfiguredValueTaskAwaiter.GetTask(out bool continueOnCapturedContext) +#if CORECLR + void IValueTaskAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box) { - continueOnCapturedContext = _continueOnCapturedContext; - return _value.AsTaskExpectNonNull(); + if (_value.ObjectIsTask) + { + TaskAwaiter.UnsafeOnCompletedInternal(_value.UnsafeGetTask(), box, _value.ContinueOnCapturedContext); + } + else if (_value._obj != null) + { + _value.UnsafeGetValueTaskSource().OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, _value._token, + _value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); + } + else + { + TaskAwaiter.UnsafeOnCompletedInternal(Task.CompletedTask, box, _value.ContinueOnCapturedContext); + } } +#endif } } - - /// <summary> - /// Internal interface used to enable extract the Task from arbitrary configured ValueTask awaiters. - /// </summary> - internal interface IConfiguredValueTaskAwaiter - { - Task GetTask(out bool continueOnCapturedContext); - } } diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TypeForwardedFromAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TypeForwardedFromAttribute.cs index c4a855824..27dd64575 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TypeForwardedFromAttribute.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TypeForwardedFromAttribute.cs @@ -9,6 +9,9 @@ namespace System.Runtime.CompilerServices { public TypeForwardedFromAttribute(string assemblyFullName) { + if (string.IsNullOrEmpty(assemblyFullName)) + throw new ArgumentNullException(nameof(assemblyFullName)); + AssemblyFullName = assemblyFullName; } diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs index 3f212d8bf..e019f6613 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs @@ -4,50 +4,198 @@ using System.Diagnostics; using System.Threading.Tasks; +using System.Threading.Tasks.Sources; namespace System.Runtime.CompilerServices { + /// <summary>Provides an awaiter for a <see cref="ValueTask"/>.</summary> + public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion +#if CORECLR + , IValueTaskAwaiter +#endif + { + /// <summary>Shim used to invoke an <see cref="Action"/> passed as the state argument to a <see cref="Action{Object}"/>.</summary> + internal static readonly Action<object> s_invokeActionDelegate = state => + { + if (!(state is Action action)) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state); + return; + } + + action(); + }; + /// <summary>The value being awaited.</summary> + private readonly ValueTask _value; + + /// <summary>Initializes the awaiter.</summary> + /// <param name="value">The value to be awaited.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ValueTaskAwaiter(ValueTask value) => _value = value; + + /// <summary>Gets whether the <see cref="ValueTask"/> has completed.</summary> + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _value.IsCompleted; + } + + /// <summary>Gets the result of the ValueTask.</summary> + [StackTraceHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void GetResult() => _value.ThrowIfCompletedUnsuccessfully(); + + /// <summary>Schedules the continuation action for this ValueTask.</summary> + public void OnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeGetTask().GetAwaiter().OnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeGetValueTaskSource().OnCompleted(s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext); + } + else + { + ValueTask.CompletedTask.GetAwaiter().OnCompleted(continuation); + } + } + + /// <summary>Schedules the continuation action for this ValueTask.</summary> + public void UnsafeOnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeGetTask().GetAwaiter().UnsafeOnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeGetValueTaskSource().OnCompleted(s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + } + else + { + ValueTask.CompletedTask.GetAwaiter().UnsafeOnCompleted(continuation); + } + } + +#if CORECLR + void IValueTaskAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box) + { + if (_value.ObjectIsTask) + { + TaskAwaiter.UnsafeOnCompletedInternal(_value.UnsafeGetTask(), box, continueOnCapturedContext: true); + } + else if (_value._obj != null) + { + _value.UnsafeGetValueTaskSource().OnCompleted(s_invokeAsyncStateMachineBox, box, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + } + else + { + TaskAwaiter.UnsafeOnCompletedInternal(Task.CompletedTask, box, continueOnCapturedContext: true); + } + } + + /// <summary>Shim used to invoke <see cref="ITaskCompletionAction.Invoke"/> of the supplied <see cref="IAsyncStateMachineBox"/>.</summary> + internal static readonly Action<object> s_invokeAsyncStateMachineBox = state => + { + if (!(state is IAsyncStateMachineBox box)) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state); + return; + } + + box.Invoke(null); + }; +#endif + } + /// <summary>Provides an awaiter for a <see cref="ValueTask{TResult}"/>.</summary> - public readonly struct ValueTaskAwaiter<TResult> : ICriticalNotifyCompletion, IValueTaskAwaiter + public readonly struct ValueTaskAwaiter<TResult> : ICriticalNotifyCompletion +#if CORECLR + , IValueTaskAwaiter +#endif { /// <summary>The value being awaited.</summary> private readonly ValueTask<TResult> _value; /// <summary>Initializes the awaiter.</summary> /// <param name="value">The value to be awaited.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ValueTaskAwaiter(ValueTask<TResult> value) => _value = value; /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> has completed.</summary> - public bool IsCompleted => _value.IsCompleted; + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _value.IsCompleted; + } /// <summary>Gets the result of the ValueTask.</summary> [StackTraceHidden] - public TResult GetResult() => - _value._task == null ? - _value._result : - _value._task.GetAwaiter().GetResult(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TResult GetResult() => _value.Result; /// <summary>Schedules the continuation action for this ValueTask.</summary> - public void OnCompleted(Action continuation) => - _value.AsTask().ConfigureAwait(continueOnCapturedContext: true).GetAwaiter().OnCompleted(continuation); + public void OnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeGetTask().GetAwaiter().OnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeGetValueTaskSource().OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext); + } + else + { + ValueTask.CompletedTask.GetAwaiter().OnCompleted(continuation); + } + } /// <summary>Schedules the continuation action for this ValueTask.</summary> - public void UnsafeOnCompleted(Action continuation) => - _value.AsTask().ConfigureAwait(continueOnCapturedContext: true).GetAwaiter().UnsafeOnCompleted(continuation); - - /// <summary>Gets the task underlying <see cref="_value"/>.</summary> - internal Task<TResult> AsTask() => _value.AsTask(); + public void UnsafeOnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeGetTask().GetAwaiter().UnsafeOnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeGetValueTaskSource().OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + } + else + { + ValueTask.CompletedTask.GetAwaiter().UnsafeOnCompleted(continuation); + } + } - /// <summary>Gets the task underlying the incomplete <see cref="_value"/>.</summary> - /// <remarks>This method is used when awaiting and IsCompleted returned false; thus we expect the value task to be wrapping a non-null task.</remarks> - Task IValueTaskAwaiter.GetTask() => _value.AsTaskExpectNonNull(); +#if CORECLR + void IValueTaskAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box) + { + if (_value.ObjectIsTask) + { + TaskAwaiter.UnsafeOnCompletedInternal(_value.UnsafeGetTask(), box, continueOnCapturedContext: true); + } + else if (_value._obj != null) + { + _value.UnsafeGetValueTaskSource().OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + } + else + { + TaskAwaiter.UnsafeOnCompletedInternal(Task.CompletedTask, box, continueOnCapturedContext: true); + } + } +#endif } - /// <summary> - /// Internal interface used to enable extract the Task from arbitrary ValueTask awaiters. - /// </summary>> +#if CORECLR + /// <summary>Internal interface used to enable optimizations from <see cref="AsyncTaskMethodBuilder"/> on <see cref="ValueTask"/>.</summary>> internal interface IValueTaskAwaiter { - Task GetTask(); + /// <summary>Invoked to set <see cref="ITaskCompletionAction.Invoke"/> of the <paramref name="box"/> as the awaiter's continuation.</summary> + /// <param name="box">The box object.</param> + void AwaitUnsafeOnCompleted(IAsyncStateMachineBox box); } +#endif } diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.Fast.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.Fast.cs index 967459e4b..34881e77a 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.Fast.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.Fast.cs @@ -29,17 +29,31 @@ namespace System.Runtime.InteropServices /// <summary> /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element - /// would have been stored. Such a reference can be used for pinning but must never be dereferenced. + /// would have been stored. Such a reference may or may not be null. It can be used for pinning but must never be dereferenced. /// </summary> public static ref T GetReference<T>(Span<T> span) => ref span._pointer.Value; /// <summary> - /// Returns a reference to the 0th element of the ReadOnlySpan. If the Span is empty, returns a reference to the location where the 0th element - /// would have been stored. Such a reference can be used for pinning but must never be dereferenced. + /// Returns a reference to the 0th element of the ReadOnlySpan. If the ReadOnlySpan is empty, returns a reference to the location where the 0th element + /// would have been stored. Such a reference may or may not be null. It can be used for pinning but must never be dereferenced. /// </summary> public static ref T GetReference<T>(ReadOnlySpan<T> span) => ref span._pointer.Value; /// <summary> + /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to fake non-null pointer. Such a reference can be used + /// for pinning but must never be dereferenced. This is useful for interop with methods that do not accept null pointers for zero-sized buffers. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe ref T GetNonNullPinnableReference<T>(Span<T> span) => ref (span.Length != 0) ? ref span._pointer.Value : ref Unsafe.AsRef<T>((void*)1); + + /// <summary> + /// Returns a reference to the 0th element of the ReadOnlySpan. If the ReadOnlySpan is empty, returns a reference to fake non-null pointer. Such a reference + /// can be used for pinning but must never be dereferenced. This is useful for interop with methods that do not accept null pointers for zero-sized buffers. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe ref T GetNonNullPinnableReference<T>(ReadOnlySpan<T> span) => ref (span.Length != 0) ? ref span._pointer.Value : ref Unsafe.AsRef<T>((void*)1); + + /// <summary> /// Casts a Span of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>. /// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety. /// </summary> @@ -50,6 +64,7 @@ namespace System.Runtime.InteropServices /// <exception cref="System.ArgumentException"> /// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers. /// </exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span<TTo> Cast<TFrom, TTo>(Span<TFrom> source) where TFrom : struct where TTo : struct @@ -59,9 +74,38 @@ namespace System.Runtime.InteropServices if (RuntimeHelpers.IsReferenceOrContainsReferences<TTo>()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo)); + // Use unsigned integers - unsigned division by constant (especially by power of 2) + // and checked casts are faster and smaller. + uint fromSize = (uint)Unsafe.SizeOf<TFrom>(); + uint toSize = (uint)Unsafe.SizeOf<TTo>(); + uint fromLength = (uint)source.Length; + int toLength; + if (fromSize == toSize) + { + // Special case for same size types - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize` + // should be optimized to just `length` but the JIT doesn't do that today. + toLength = (int)fromLength; + } + else if (fromSize == 1) + { + // Special case for byte sized TFrom - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize` + // becomes `(ulong)fromLength / (ulong)toSize` but the JIT can't narrow it down to `int` + // and can't eliminate the checked cast. This also avoids a 32 bit specific issue, + // the JIT can't eliminate long multiply by 1. + toLength = (int)(fromLength / toSize); + } + else + { + // Ensure that casts are done in such a way that the JIT is able to "see" + // the uint->ulong casts and the multiply together so that on 32 bit targets + // 32x32to64 multiplication is used. + ulong toLengthUInt64 = (ulong)fromLength * (ulong)fromSize / (ulong)toSize; + toLength = checked((int)toLengthUInt64); + } + return new Span<TTo>( ref Unsafe.As<TFrom, TTo>(ref source._pointer.Value), - checked((int)((long)source.Length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()))); + toLength); } /// <summary> @@ -75,6 +119,7 @@ namespace System.Runtime.InteropServices /// <exception cref="System.ArgumentException"> /// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers. /// </exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> source) where TFrom : struct where TTo : struct @@ -84,9 +129,38 @@ namespace System.Runtime.InteropServices if (RuntimeHelpers.IsReferenceOrContainsReferences<TTo>()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo)); + // Use unsigned integers - unsigned division by constant (especially by power of 2) + // and checked casts are faster and smaller. + uint fromSize = (uint)Unsafe.SizeOf<TFrom>(); + uint toSize = (uint)Unsafe.SizeOf<TTo>(); + uint fromLength = (uint)source.Length; + int toLength; + if (fromSize == toSize) + { + // Special case for same size types - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize` + // should be optimized to just `length` but the JIT doesn't do that today. + toLength = (int)fromLength; + } + else if (fromSize == 1) + { + // Special case for byte sized TFrom - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize` + // becomes `(ulong)fromLength / (ulong)toSize` but the JIT can't narrow it down to `int` + // and can't eliminate the checked cast. This also avoids a 32 bit specific issue, + // the JIT can't eliminate long multiply by 1. + toLength = (int)(fromLength / toSize); + } + else + { + // Ensure that casts are done in such a way that the JIT is able to "see" + // the uint->ulong casts and the multiply together so that on 32 bit targets + // 32x32to64 multiplication is used. + ulong toLengthUInt64 = (ulong)fromLength * (ulong)fromSize / (ulong)toSize; + toLength = checked((int)toLengthUInt64); + } + return new ReadOnlySpan<TTo>( ref Unsafe.As<TFrom, TTo>(ref MemoryMarshal.GetReference(source)), - checked((int)((long)source.Length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()))); + toLength); } /// <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 6544081df..316ce12aa 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs @@ -98,5 +98,29 @@ namespace System.Runtime.InteropServices for (int i = 0; i < memory.Length; i++) yield return memory.Span[i]; } + + /// <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(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/Single.cs b/src/System.Private.CoreLib/shared/System/Single.cs index 7bffa1ac7..013dd9b40 100644 --- a/src/System.Private.CoreLib/shared/System/Single.cs +++ b/src/System.Private.CoreLib/shared/System/Single.cs @@ -335,15 +335,15 @@ namespace System if (!success) { ReadOnlySpan<char> sTrim = s.Trim(); - if (StringSpanHelpers.Equals(sTrim, info.PositiveInfinitySymbol)) + if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol)) { result = PositiveInfinity; } - else if (StringSpanHelpers.Equals(sTrim, info.NegativeInfinitySymbol)) + else if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol)) { result = NegativeInfinity; } - else if (StringSpanHelpers.Equals(sTrim, info.NaNSymbol)) + else if (sTrim.EqualsOrdinal(info.NaNSymbol)) { result = NaN; } diff --git a/src/System.Private.CoreLib/shared/System/Span.NonGeneric.cs b/src/System.Private.CoreLib/shared/System/Span.NonGeneric.cs deleted file mode 100644 index 4f2892d4c..000000000 --- a/src/System.Private.CoreLib/shared/System/Span.NonGeneric.cs +++ /dev/null @@ -1,41 +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.Globalization; -using System.Runtime; -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 -{ - /// <summary> - /// Extension methods and non-generic helpers for Span, ReadOnlySpan, Memory, and ReadOnlyMemory. - /// </summary> - public static class Span - { - public static Span<byte> AsBytes<T>(Span<T> source) - where T : struct => source.AsBytes(); - - public static ReadOnlySpan<byte> AsBytes<T>(ReadOnlySpan<T> source) - where T : struct => source.AsBytes(); - - // 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); - - public static ReadOnlyMemory<char> AsReadOnlyMemory(this string text) => text.AsMemory(); - public static ReadOnlyMemory<char> AsReadOnlyMemory(this string text, int start) => text.AsMemory(start); - public static ReadOnlyMemory<char> AsReadOnlyMemory(this string text, int start, int length) => text.AsMemory(start, length); - } -} diff --git a/src/System.Private.CoreLib/shared/System/SpanHelpers.cs b/src/System.Private.CoreLib/shared/System/SpanHelpers.cs index dad0f6294..6752d7fef 100644 --- a/src/System.Private.CoreLib/shared/System/SpanHelpers.cs +++ b/src/System.Private.CoreLib/shared/System/SpanHelpers.cs @@ -461,43 +461,78 @@ namespace System public static unsafe void ClearWithReferences(ref IntPtr ip, nuint pointerSizeLength) { - if (pointerSizeLength == 0) - return; + Debug.Assert((int)Unsafe.AsPointer(ref ip) % sizeof(IntPtr) == 0, "Should've been aligned on natural word boundary."); - // TODO: Perhaps do switch casing to improve small size perf + // First write backward 8 natural words at a time. + // Writing backward allows us to get away with only simple modifications to the + // mov instruction's base and index registers between loop iterations. - nuint i = 0; - nuint n = 0; - while ((n = i + 8) <= (pointerSizeLength)) + for (; pointerSizeLength >= 8; pointerSizeLength -= 8) { - 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; + Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -1) = default(IntPtr); + Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -2) = default(IntPtr); + Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -3) = default(IntPtr); + Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -4) = default(IntPtr); + Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -5) = default(IntPtr); + Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -6) = default(IntPtr); + Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -7) = default(IntPtr); + Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -8) = default(IntPtr); } - if ((n = i + 4) <= (pointerSizeLength)) + + Debug.Assert(pointerSizeLength <= 7); + + // The logic below works by trying to minimize the number of branches taken for any + // given range of lengths. For example, the lengths [ 4 .. 7 ] are handled by a single + // branch, [ 2 .. 3 ] are handled by a single branch, and [ 1 ] is handled by a single + // branch. + // + // We can write both forward and backward as a perf improvement. For example, + // the lengths [ 4 .. 7 ] can be handled by zeroing out the first four natural + // words and the last 3 natural words. In the best case (length = 7), there are + // no overlapping writes. In the worst case (length = 4), there are three + // overlapping writes near the middle of the buffer. In perf testing, the + // penalty for performing duplicate writes is less expensive than the penalty + // for complex branching. + + if (pointerSizeLength >= 4) { - 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; + goto Write4To7; } - if ((n = i + 2) <= (pointerSizeLength)) + else if (pointerSizeLength >= 2) { - 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; + goto Write2To3; } - if ((i + 1) <= (pointerSizeLength)) + else if (pointerSizeLength > 0) { - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr); + goto Write1; } + else + { + return; // nothing to write + } + + Write4To7: + Debug.Assert(pointerSizeLength >= 4); + + // Write first four and last three. + Unsafe.Add(ref ip, 2) = default(IntPtr); + Unsafe.Add(ref ip, 3) = default(IntPtr); + Unsafe.Add(ref ip, 4) = default(IntPtr); + Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -3) = default(IntPtr); + Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -2) = default(IntPtr); + + Write2To3: + Debug.Assert(pointerSizeLength >= 2); + + // Write first two and last one. + Unsafe.Add(ref ip, 1) = default(IntPtr); + Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -1) = default(IntPtr); + + Write1: + Debug.Assert(pointerSizeLength >= 1); + + // Write only element. + ip = default(IntPtr); } } } diff --git a/src/System.Private.CoreLib/shared/System/String.Searching.cs b/src/System.Private.CoreLib/shared/System/String.Searching.cs index 8a67feff3..c86d13524 100644 --- a/src/System.Private.CoreLib/shared/System/String.Searching.cs +++ b/src/System.Private.CoreLib/shared/System/String.Searching.cs @@ -3,7 +3,10 @@ // See the LICENSE file in the project root for more information. using System.Globalization; +using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Internal.Runtime.CompilerServices; namespace System { @@ -63,24 +66,35 @@ namespace System case StringComparison.OrdinalIgnoreCase: return CompareInfo.Invariant.IndexOf(this, value, CompareOptions.OrdinalIgnoreCase); - + default: throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); } } - + public unsafe int IndexOf(char value, int startIndex, int count) { - if (startIndex < 0 || startIndex > Length) + if ((uint)startIndex > (uint)Length) throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index); - if (count < 0 || count > Length - startIndex) + if ((uint)count > (uint)(Length - startIndex)) throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count); fixed (char* pChars = &_firstChar) { char* pCh = pChars + startIndex; + char* pEndCh = pCh + count; + if (Vector.IsHardwareAccelerated && count >= Vector<ushort>.Count * 2) + { + unchecked + { + const int elementsPerByte = sizeof(ushort) / sizeof(byte); + int unaligned = ((int)pCh & (Vector<byte>.Count - 1)) / elementsPerByte; + count = ((Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1)); + } + } + SequentialScan: while (count >= 4) { if (*pCh == value) goto ReturnIndex; @@ -101,6 +115,34 @@ namespace System pCh++; } + if (pCh < pEndCh) + { + count = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1)); + // Get comparison Vector + Vector<ushort> vComparison = new Vector<ushort>(value); + while (count > 0) + { + var vMatches = Vector.Equals(vComparison, Unsafe.ReadUnaligned<Vector<ushort>>(pCh)); + if (Vector<ushort>.Zero.Equals(vMatches)) + { + pCh += Vector<ushort>.Count; + count -= Vector<ushort>.Count; + continue; + } + // Find offset of first match + return (int)(pCh - pChars) + LocateFirstFoundChar(vMatches); + } + + if (pCh < pEndCh) + { + unchecked + { + count = (int)(pEndCh - pCh); + } + goto SequentialScan; + } + } + return -1; ReturnIndex3: pCh++; @@ -111,6 +153,43 @@ namespace System } } + // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LocateFirstFoundChar(Vector<ushort> 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 * 4 + LocateFirstFoundChar(candidate); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LocateFirstFoundChar(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 * XorPowerOfTwoToHighChar) >> 49); + } + } + + private const ulong XorPowerOfTwoToHighChar = (0x03ul | + 0x02ul << 16 | + 0x01ul << 32) + 1; + // Returns the index of the first occurrence of any specified character in the current instance. // The search starts at startIndex and runs to startIndex + count - 1. // @@ -397,17 +476,27 @@ namespace System if (Length == 0) return -1; - if (startIndex < 0 || startIndex >= Length) + if ((uint)startIndex >= (uint)Length) throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index); - if (count < 0 || count - 1 > startIndex) + if ((uint)count > (uint)startIndex + 1) throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count); fixed (char* pChars = &_firstChar) { char* pCh = pChars + startIndex; + char* pEndCh = pCh - count; //We search [startIndex..EndIndex] + if (Vector.IsHardwareAccelerated && count >= Vector<ushort>.Count * 2) + { + unchecked + { + const int elementsPerByte = sizeof(ushort) / sizeof(byte); + count = (((int)pCh & (Vector<byte>.Count - 1)) / elementsPerByte) + 1; + } + } + SequentialScan: while (count >= 4) { if (*pCh == value) goto ReturnIndex; @@ -428,6 +517,35 @@ namespace System pCh--; } + if (pCh > pEndCh) + { + count = (int)((pCh - pEndCh) & ~(Vector<ushort>.Count - 1)); + + // Get comparison Vector + Vector<ushort> vComparison = new Vector<ushort>(value); + while (count > 0) + { + char* pStart = pCh - Vector<ushort>.Count + 1; + var vMatches = Vector.Equals(vComparison, Unsafe.ReadUnaligned<Vector<ushort>>(pStart)); + if (Vector<ushort>.Zero.Equals(vMatches)) + { + pCh -= Vector<ushort>.Count; + count -= Vector<ushort>.Count; + continue; + } + // Find offset of last match + return (int)(pStart - pChars) + LocateLastFoundChar(vMatches); + } + + if (pCh > pEndCh) + { + unchecked + { + count = (int)(pCh - pEndCh); + } + goto SequentialScan; + } + } return -1; ReturnIndex3: pCh--; @@ -438,6 +556,40 @@ namespace System } } + // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LocateLastFoundChar(Vector<ushort> 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 * 4 + LocateLastFoundChar(candidate); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LocateLastFoundChar(ulong match) + { + // Find the most significant char that has its highest bit set + int index = 3; + while ((long)match > 0) + { + match = match << 16; + index--; + } + return index; + } + // Returns the index of the last occurrence of any specified character in the current instance. // The search starts at startIndex and runs backwards to startIndex - count + 1. // The character at position startIndex is included in the search. startIndex is the larger diff --git a/src/System.Private.CoreLib/shared/System/StringSpanHelpers.cs b/src/System.Private.CoreLib/shared/System/StringSpanHelpers.cs deleted file mode 100644 index 2d6152de5..000000000 --- a/src/System.Private.CoreLib/shared/System/StringSpanHelpers.cs +++ /dev/null @@ -1,139 +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.Globalization; - -namespace System -{ - /// <summary>Helpers for string-like operations on spans of chars.</summary> - internal static class StringSpanHelpers - { - // TODO https://github.com/dotnet/corefx/issues/21395: Provide public, efficient implementations - - public static bool Equals(this ReadOnlySpan<char> left, ReadOnlySpan<char> right, StringComparison comparisonType) => - comparisonType == StringComparison.Ordinal ? Equals(left, right) : - comparisonType == StringComparison.OrdinalIgnoreCase ? EqualsOrdinalIgnoreCase(left, right) : - throw new ArgumentOutOfRangeException(nameof(comparisonType)); - - public static bool Equals(this ReadOnlySpan<char> left, string right) => - Equals(left, right.AsSpan()); - - public static bool Equals(this ReadOnlySpan<char> left, ReadOnlySpan<char> right) - { - if (left.Length != right.Length) - { - return false; - } - - for (int i = 0; i < left.Length; i++) - { - if (left[i] != right[i]) - { - return false; - } - } - - return true; - } - - private static bool EqualsOrdinalIgnoreCase(this ReadOnlySpan<char> left, ReadOnlySpan<char> right) - { - if (left.Length != right.Length) - { - return false; - } - - for (int i = 0; i < left.Length; i++) - { - char x = left[i], y = right[i]; - if (x != y && - TextInfo.ToUpperAsciiInvariant(x) != TextInfo.ToUpperAsciiInvariant(y)) - { - return false; - } - } - - return true; - } - - public static int IndexOf(this ReadOnlySpan<char> source, char value) => - IndexOf(source, value, 0); - - public static int IndexOf(this ReadOnlySpan<char> source, char value, int startIndex) - { - for (int i = startIndex; i < source.Length; i++) - { - if (source[i] == value) - { - return i; - } - } - - return -1; - } - - public static bool Contains(this ReadOnlySpan<char> source, char value) - { - for (int i = 0; i < source.Length; i++) - { - if (source[i] == value) - { - return true; - } - } - - return false; - } - - public static ReadOnlySpan<char> Remove(this ReadOnlySpan<char> source, int startIndex, int count) - { - if (startIndex < 0) - throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex); - if (count < 0) - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NegativeCount); - if (count > source.Length - startIndex) - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_IndexCount); - - if (count == 0) - { - return source; - } - - int newLength = source.Length - count; - if (newLength == 0) - { - return ReadOnlySpan<char>.Empty; - } - - Span<char> result = new char[newLength]; - source.Slice(0, startIndex).CopyTo(result); - source.Slice(startIndex + count).CopyTo(result.Slice(startIndex)); - return result; - } - - // Returns the index of the last occurrence of a specified character in the current instance. - public static int LastIndexOf(this ReadOnlySpan<char> source, char value) - { - if (source.Length == 0) - return -1; - - for (int i = source.Length - 1; i >= 0; i--) - { - if (source[i] == value) - return i; - } - - return -1; - } - - public static void CheckStringComparison(StringComparison comparisonType) - { - // Single comparison to check if comparisonType is within [CurrentCulture .. OrdinalIgnoreCase] - if ((uint)(comparisonType - StringComparison.CurrentCulture) > (StringComparison.OrdinalIgnoreCase - StringComparison.CurrentCulture)) - { - throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); - } - } - } -} diff --git a/src/System.Private.CoreLib/shared/System/Text/Decoder.cs b/src/System.Private.CoreLib/shared/System/Text/Decoder.cs index b827648fc..b4a7575ba 100644 --- a/src/System.Private.CoreLib/shared/System/Text/Decoder.cs +++ b/src/System.Private.CoreLib/shared/System/Text/Decoder.cs @@ -134,7 +134,7 @@ namespace System.Text public virtual unsafe int GetCharCount(ReadOnlySpan<byte> bytes, bool flush) { - fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) + fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes)) { return GetCharCount(bytesPtr, bytes.Length, flush); } @@ -227,8 +227,8 @@ namespace System.Text public virtual unsafe int GetChars(ReadOnlySpan<byte> bytes, Span<char> chars, bool flush) { - fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) - fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) + fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes)) + fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars)) { return GetChars(bytesPtr, bytes.Length, charsPtr, chars.Length, flush); } @@ -341,8 +341,8 @@ namespace System.Text public virtual unsafe void Convert(ReadOnlySpan<byte> bytes, Span<char> chars, bool flush, out int bytesUsed, out int charsUsed, out bool completed) { - fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) - fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) + fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes)) + fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars)) { Convert(bytesPtr, bytes.Length, charsPtr, chars.Length, flush, out bytesUsed, out charsUsed, out completed); } diff --git a/src/System.Private.CoreLib/shared/System/Text/Encoder.cs b/src/System.Private.CoreLib/shared/System/Text/Encoder.cs index fb1bdb803..df7d51203 100644 --- a/src/System.Private.CoreLib/shared/System/Text/Encoder.cs +++ b/src/System.Private.CoreLib/shared/System/Text/Encoder.cs @@ -132,7 +132,7 @@ namespace System.Text public virtual unsafe int GetByteCount(ReadOnlySpan<char> chars, bool flush) { - fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) + fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars)) { return GetByteCount(charsPtr, chars.Length, flush); } @@ -221,8 +221,8 @@ namespace System.Text public virtual unsafe int GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, bool flush) { - fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) - fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) + fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars)) + fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes)) { return GetBytes(charsPtr, chars.Length, bytesPtr, bytes.Length, flush); } @@ -335,8 +335,8 @@ namespace System.Text public virtual unsafe void Convert(ReadOnlySpan<char> chars, Span<byte> bytes, bool flush, out int charsUsed, out int bytesUsed, out bool completed) { - fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) - fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) + fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars)) + fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes)) { Convert(charsPtr, chars.Length, bytesPtr, bytes.Length, flush, out charsUsed, out bytesUsed, out completed); } diff --git a/src/System.Private.CoreLib/shared/System/Text/Encoding.cs b/src/System.Private.CoreLib/shared/System/Text/Encoding.cs index e469180ce..e191ce14f 100644 --- a/src/System.Private.CoreLib/shared/System/Text/Encoding.cs +++ b/src/System.Private.CoreLib/shared/System/Text/Encoding.cs @@ -713,7 +713,7 @@ namespace System.Text public virtual unsafe int GetByteCount(ReadOnlySpan<char> chars) { - fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) + fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars)) { return GetByteCount(charsPtr, chars.Length); } @@ -895,8 +895,8 @@ namespace System.Text public virtual unsafe int GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes) { - fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) - fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) + fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars)) + fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes)) { return GetBytes(charsPtr, chars.Length, bytesPtr, bytes.Length); } @@ -945,7 +945,7 @@ namespace System.Text public virtual unsafe int GetCharCount(ReadOnlySpan<byte> bytes) { - fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) + fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes)) { return GetCharCount(bytesPtr, bytes.Length); } @@ -1057,8 +1057,8 @@ namespace System.Text public virtual unsafe int GetChars(ReadOnlySpan<byte> bytes, Span<char> chars) { - fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) - fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) + fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes)) + fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars)) { return GetChars(bytesPtr, bytes.Length, charsPtr, chars.Length); } @@ -1087,7 +1087,7 @@ namespace System.Text public unsafe string GetString(ReadOnlySpan<byte> bytes) { - fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) + fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes)) { return GetString(bytesPtr, bytes.Length); } diff --git a/src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs b/src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs index a7804c399..65a4a240e 100644 --- a/src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs +++ b/src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs @@ -1696,7 +1696,7 @@ namespace System.Text ReadOnlySpan<char> chunk = new ReadOnlySpan<char>(sbChunk.m_ChunkChars, 0, chunk_length); - if (!chunk.Equals(value.Slice(value.Length - offset, chunk_length))) + if (!chunk.EqualsOrdinal(value.Slice(value.Length - offset, chunk_length))) return false; sbChunk = sbChunk.m_ChunkPrevious; diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs new file mode 100644 index 000000000..e411146a1 --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs @@ -0,0 +1,82 @@ +// 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.Threading.Tasks.Sources +{ + /// <summary> + /// Flags passed from <see cref="ValueTask"/> and <see cref="ValueTask{TResult}"/> to + /// <see cref="IValueTaskSource.OnCompleted"/> and <see cref="IValueTaskSource{TResult}.OnCompleted"/> + /// to control behavior. + /// </summary> + [Flags] + public enum ValueTaskSourceOnCompletedFlags + { + /// <summary> + /// No requirements are placed on how the continuation is invoked. + /// </summary> + None, + /// <summary> + /// Set if OnCompleted should capture the current scheduling context (e.g. SynchronizationContext) + /// and use it when queueing the continuation for execution. If this is not set, the implementation + /// may choose to execute the continuation in an arbitrary location. + /// </summary> + UseSchedulingContext = 0x1, + /// <summary> + /// Set if OnCompleted should capture the current ExecutionContext and use it to run the continuation. + /// </summary> + FlowExecutionContext = 0x2, + } + + /// <summary>Indicates the status of an <see cref="IValueTaskSource"/> or <see cref="IValueTaskSource{TResult}"/>.</summary> + public enum ValueTaskSourceStatus + { + /// <summary>The operation has not yet completed.</summary> + Pending = 0, + /// <summary>The operation completed successfully.</summary> + Succeeded = 1, + /// <summary>The operation completed with an error.</summary> + Faulted = 2, + /// <summary>The operation completed due to cancellation.</summary> + Canceled = 3 + } + + /// <summary>Represents an object that can be wrapped by a <see cref="ValueTask"/>.</summary> + public interface IValueTaskSource + { + /// <summary>Gets the status of the current operation.</summary> + /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> + ValueTaskSourceStatus GetStatus(short token); + + /// <summary>Schedules the continuation action for this <see cref="IValueTaskSource"/>.</summary> + /// <param name="continuation">The continuation to invoke when the operation has completed.</param> + /// <param name="state">The state object to pass to <paramref name="continuation"/> when it's invoked.</param> + /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> + /// <param name="flags">The flags describing the behavior of the continuation.</param> + void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags); + + /// <summary>Gets the result of the <see cref="IValueTaskSource"/>.</summary> + /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> + void GetResult(short token); + } + + /// <summary>Represents an object that can be wrapped by a <see cref="ValueTask{TResult}"/>.</summary> + /// <typeparam name="TResult">Specifies the type of data returned from the object.</typeparam> + public interface IValueTaskSource<out TResult> + { + /// <summary>Gets the status of the current operation.</summary> + /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> + ValueTaskSourceStatus GetStatus(short token); + + /// <summary>Schedules the continuation action for this <see cref="IValueTaskSource{TResult}"/>.</summary> + /// <param name="continuation">The continuation to invoke when the operation has completed.</param> + /// <param name="state">The state object to pass to <paramref name="continuation"/> when it's invoked.</param> + /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> + /// <param name="flags">The flags describing the behavior of the continuation.</param> + void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags); + + /// <summary>Gets the result of the <see cref="IValueTaskSource{TResult}"/>.</summary> + /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> + TResult GetResult(short token); + } +} diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs index 5edd8501b..8190921d0 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs @@ -3,71 +3,401 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading.Tasks.Sources; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif namespace System.Threading.Tasks { - /// <summary> - /// Provides a value type that wraps a <see cref="Task{TResult}"/> and a <typeparamref name="TResult"/>, - /// only one of which is used. - /// </summary> - /// <typeparam name="TResult">The type of the result.</typeparam> + /// <summary>Provides an awaitable result of an asynchronous operation.</summary> /// <remarks> - /// <para> - /// Methods may return an instance of this value type when it's likely that the result of their - /// operations will be available synchronously and when the method is expected to be invoked so - /// frequently that the cost of allocating a new <see cref="Task{TResult}"/> for each call will - /// be prohibitive. - /// </para> - /// <para> - /// There are tradeoffs to using a <see cref="ValueTask{TResult}"/> instead of a <see cref="Task{TResult}"/>. - /// For example, while a <see cref="ValueTask{TResult}"/> can help avoid an allocation in the case where the - /// successful result is available synchronously, it also contains two fields whereas a <see cref="Task{TResult}"/> - /// as a reference type is a single field. This means that a method call ends up returning two fields worth of - /// data instead of one, which is more data to copy. It also means that if a method that returns one of these - /// is awaited within an async method, the state machine for that async method will be larger due to needing - /// to store the struct that's two fields instead of a single reference. - /// </para> - /// <para> - /// Further, for uses other than consuming the result of an asynchronous operation via await, - /// <see cref="ValueTask{TResult}"/> can lead to a more convoluted programming model, which can in turn actually - /// lead to more allocations. For example, consider a method that could return either a <see cref="Task{TResult}"/> - /// with a cached task as a common result or a <see cref="ValueTask{TResult}"/>. If the consumer of the result - /// wants to use it as a <see cref="Task{TResult}"/>, such as to use with in methods like Task.WhenAll and Task.WhenAny, - /// the <see cref="ValueTask{TResult}"/> would first need to be converted into a <see cref="Task{TResult}"/> using - /// <see cref="ValueTask{TResult}.AsTask"/>, which leads to an allocation that would have been avoided if a cached - /// <see cref="Task{TResult}"/> had been used in the first place. - /// </para> - /// <para> - /// As such, the default choice for any asynchronous method should be to return a <see cref="Task"/> or - /// <see cref="Task{TResult}"/>. Only if performance analysis proves it worthwhile should a <see cref="ValueTask{TResult}"/> - /// be used instead of <see cref="Task{TResult}"/>. There is no non-generic version of <see cref="ValueTask{TResult}"/> - /// as the Task.CompletedTask property may be used to hand back a successfully completed singleton in the case where - /// a <see cref="Task"/>-returning method completes synchronously and successfully. - /// </para> + /// <see cref="ValueTask"/>s are meant to be directly awaited. To do more complicated operations with them, a <see cref="Task"/> + /// should be extracted using <see cref="AsTask"/>. Such operations might include caching an instance to be awaited later, + /// registering multiple continuations with a single operation, awaiting the same task multiple times, and using combinators over + /// multiple operations. + /// </remarks> + [AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder))] + [StructLayout(LayoutKind.Auto)] + public readonly struct ValueTask : IEquatable<ValueTask> + { + internal static Task CompletedTask +#if netstandard + { get; } = Task.Delay(0); +#else + => Task.CompletedTask; +#endif + + /// <summary>null if representing a successful synchronous completion, otherwise a <see cref="Task"/> or a <see cref="IValueTaskSource"/>.</summary> + internal readonly object _obj; + /// <summary>Flags providing additional details about the ValueTask's contents and behavior.</summary> + internal readonly ValueTaskFlags _flags; + /// <summary>Opaque value passed through to the <see cref="IValueTaskSource"/>.</summary> + internal readonly short _token; + + // An instance created with the default ctor (a zero init'd struct) represents a synchronously, successfully completed operation. + + /// <summary>Initialize the <see cref="ValueTask"/> with a <see cref="Task"/> that represents the operation.</summary> + /// <param name="task">The task.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask(Task task) + { + if (task == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.task); + } + + _obj = task; + + _flags = ValueTaskFlags.ObjectIsTask; + _token = 0; + } + + /// <summary>Initialize the <see cref="ValueTask"/> with a <see cref="IValueTaskSource"/> object that represents the operation.</summary> + /// <param name="source">The source.</param> + /// <param name="token">Opaque value passed through to the <see cref="IValueTaskSource"/>.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask(IValueTaskSource source, short token) + { + if (source == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + _obj = source; + _token = token; + + _flags = 0; + } + + /// <summary>Non-verified initialization of the struct to the specified values.</summary> + /// <param name="obj">The object.</param> + /// <param name="token">The token.</param> + /// <param name="flags">The flags.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ValueTask(object obj, short token, ValueTaskFlags flags) + { + _obj = obj; + _token = token; + _flags = flags; + } + + /// <summary>Gets whether the contination should be scheduled to the current context.</summary> + internal bool ContinueOnCapturedContext + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (_flags & ValueTaskFlags.AvoidCapturedContext) == 0; + } + + /// <summary>Gets whether the object in the <see cref="_obj"/> field is a <see cref="Task"/>.</summary> + internal bool ObjectIsTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (_flags & ValueTaskFlags.ObjectIsTask) != 0; + } + + /// <summary>Returns the <see cref="Task"/> stored in <see cref="_obj"/>. This uses <see cref="Unsafe"/>.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Task UnsafeGetTask() + { + Debug.Assert(ObjectIsTask); + Debug.Assert(_obj is Task); + return Unsafe.As<Task>(_obj); + } + + /// <summary>Returns the <see cref="IValueTaskSource"/> stored in <see cref="_obj"/>. This uses <see cref="Unsafe"/>.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal IValueTaskSource UnsafeGetValueTaskSource() + { + Debug.Assert(!ObjectIsTask); + Debug.Assert(_obj is IValueTaskSource); + return Unsafe.As<IValueTaskSource>(_obj); + } + + /// <summary>Returns the hash code for this instance.</summary> + public override int GetHashCode() => _obj?.GetHashCode() ?? 0; + + /// <summary>Returns a value indicating whether this value is equal to a specified <see cref="object"/>.</summary> + public override bool Equals(object obj) => + obj is ValueTask && + Equals((ValueTask)obj); + + /// <summary>Returns a value indicating whether this value is equal to a specified <see cref="ValueTask"/> value.</summary> + public bool Equals(ValueTask other) => _obj == other._obj && _token == other._token; + + /// <summary>Returns a value indicating whether two <see cref="ValueTask"/> values are equal.</summary> + public static bool operator ==(ValueTask left, ValueTask right) => + left.Equals(right); + + /// <summary>Returns a value indicating whether two <see cref="ValueTask"/> values are not equal.</summary> + public static bool operator !=(ValueTask left, ValueTask right) => + !left.Equals(right); + + /// <summary> + /// Gets a <see cref="Task"/> object to represent this ValueTask. + /// </summary> + /// <remarks> + /// It will either return the wrapped task object if one exists, or it'll + /// manufacture a new task object to represent the result. + /// </remarks> + public Task AsTask() => + _obj == null ? ValueTask.CompletedTask : + ObjectIsTask ? UnsafeGetTask() : + GetTaskForValueTaskSource(); + + /// <summary>Gets a <see cref="ValueTask"/> that may be used at any point in the future.</summary> + public ValueTask Preserve() => _obj == null ? this : new ValueTask(AsTask()); + + /// <summary>Creates a <see cref="Task"/> to represent the <see cref="IValueTaskSource"/>.</summary> + private Task GetTaskForValueTaskSource() + { + IValueTaskSource t = UnsafeGetValueTaskSource(); + ValueTaskSourceStatus status = t.GetStatus(_token); + if (status != ValueTaskSourceStatus.Pending) + { + try + { + // Propagate any exceptions that may have occurred, then return + // an already successfully completed task. + t.GetResult(_token); + return ValueTask.CompletedTask; + + // If status is Faulted or Canceled, GetResult should throw. But + // we can't guarantee every implementation will do the "right thing". + // If it doesn't throw, we just treat that as success and ignore + // the status. + } + catch (Exception exc) + { + if (status == ValueTaskSourceStatus.Canceled) + { +#if netstandard + var tcs = new TaskCompletionSource<bool>(); + tcs.TrySetCanceled(); + return tcs.Task; +#else + if (exc is OperationCanceledException oce) + { + var task = new Task<VoidTaskResult>(); + task.TrySetCanceled(oce.CancellationToken, oce); + return task; + } + else + { + return Task.FromCanceled(new CancellationToken(true)); + } +#endif + } + else + { +#if netstandard + var tcs = new TaskCompletionSource<bool>(); + tcs.TrySetException(exc); + return tcs.Task; +#else + return Task.FromException(exc); +#endif + } + } + } + + var m = new ValueTaskSourceTask(t, _token); + return +#if netstandard + m.Task; +#else + m; +#endif + } + + /// <summary>Type used to create a <see cref="Task"/> to represent a <see cref="IValueTaskSource"/>.</summary> + private sealed class ValueTaskSourceTask : +#if netstandard + TaskCompletionSource<bool> +#else + Task<VoidTaskResult> +#endif + { + private static readonly Action<object> s_completionAction = state => + { + if (!(state is ValueTaskSourceTask vtst) || + !(vtst._source is IValueTaskSource source)) + { + // This could only happen if the IValueTaskSource passed the wrong state + // or if this callback were invoked multiple times such that the state + // was previously nulled out. + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state); + return; + } + + vtst._source = null; + ValueTaskSourceStatus status = source.GetStatus(vtst._token); + try + { + source.GetResult(vtst._token); + vtst.TrySetResult(default); + } + catch (Exception exc) + { + if (status == ValueTaskSourceStatus.Canceled) + { +#if netstandard + vtst.TrySetCanceled(); +#else + if (exc is OperationCanceledException oce) + { + vtst.TrySetCanceled(oce.CancellationToken, oce); + } + else + { + vtst.TrySetCanceled(new CancellationToken(true)); + } +#endif + } + else + { + vtst.TrySetException(exc); + } + } + }; + + /// <summary>The associated <see cref="IValueTaskSource"/>.</summary> + private IValueTaskSource _source; + /// <summary>The token to pass through to operations on <see cref="_source"/></summary> + private readonly short _token; + + public ValueTaskSourceTask(IValueTaskSource source, short token) + { + _token = token; + _source = source; + source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None); + } + } + + /// <summary>Gets whether the <see cref="ValueTask"/> represents a completed operation.</summary> + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _obj == null || (ObjectIsTask ? UnsafeGetTask().IsCompleted : UnsafeGetValueTaskSource().GetStatus(_token) != ValueTaskSourceStatus.Pending); + } + + /// <summary>Gets whether the <see cref="ValueTask"/> represents a successfully completed operation.</summary> + public bool IsCompletedSuccessfully + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => + _obj == null || + (ObjectIsTask ? +#if netstandard + UnsafeGetTask().Status == TaskStatus.RanToCompletion : +#else + UnsafeGetTask().IsCompletedSuccessfully : +#endif + UnsafeGetValueTaskSource().GetStatus(_token) == ValueTaskSourceStatus.Succeeded); + } + + /// <summary>Gets whether the <see cref="ValueTask"/> represents a failed operation.</summary> + public bool IsFaulted + { + get => + _obj != null && + (ObjectIsTask ? UnsafeGetTask().IsFaulted : UnsafeGetValueTaskSource().GetStatus(_token) == ValueTaskSourceStatus.Faulted); + } + + /// <summary>Gets whether the <see cref="ValueTask"/> represents a canceled operation.</summary> + /// <remarks> + /// If the <see cref="ValueTask"/> is backed by a result or by a <see cref="IValueTaskSource"/>, + /// this will always return false. If it's backed by a <see cref="Task"/>, it'll return the + /// value of the task's <see cref="Task.IsCanceled"/> property. + /// </remarks> + public bool IsCanceled + { + get => + _obj != null && + (ObjectIsTask ? UnsafeGetTask().IsCanceled : UnsafeGetValueTaskSource().GetStatus(_token) == ValueTaskSourceStatus.Canceled); + } + + /// <summary>Throws the exception that caused the <see cref="ValueTask"/> to fail. If it completed successfully, nothing is thrown.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + internal void ThrowIfCompletedUnsuccessfully() + { + if (_obj != null) + { + if (ObjectIsTask) + { +#if netstandard + UnsafeGetTask().GetAwaiter().GetResult(); +#else + TaskAwaiter.ValidateEnd(UnsafeGetTask()); +#endif + } + else + { + UnsafeGetValueTaskSource().GetResult(_token); + } + } + } + + /// <summary>Gets an awaiter for this <see cref="ValueTask"/>.</summary> + public ValueTaskAwaiter GetAwaiter() => new ValueTaskAwaiter(this); + + /// <summary>Configures an awaiter for this <see cref="ValueTask"/>.</summary> + /// <param name="continueOnCapturedContext"> + /// true to attempt to marshal the continuation back to the captured context; otherwise, false. + /// </param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) + { + // TODO: Simplify once https://github.com/dotnet/coreclr/pull/16138 is fixed. + bool avoidCapture = !continueOnCapturedContext; + return new ConfiguredValueTaskAwaitable(new ValueTask(_obj, _token, _flags | Unsafe.As<bool, ValueTaskFlags>(ref avoidCapture))); + } + } + + /// <summary>Provides a value type that can represent a synchronously available value or a task object.</summary> + /// <typeparam name="TResult">Specifies the type of the result.</typeparam> + /// <remarks> + /// <see cref="ValueTask{TResult}"/>s are meant to be directly awaited. To do more complicated operations with them, a <see cref="Task"/> + /// should be extracted using <see cref="AsTask"/> or <see cref="Preserve"/>. Such operations might include caching an instance to + /// be awaited later, registering multiple continuations with a single operation, awaiting the same task multiple times, and using + /// combinators over multiple operations. /// </remarks> [AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder<>))] [StructLayout(LayoutKind.Auto)] public readonly struct ValueTask<TResult> : IEquatable<ValueTask<TResult>> { - /// <summary>The task to be used if the operation completed asynchronously or if it completed synchronously but non-successfully.</summary> - internal readonly Task<TResult> _task; + /// <summary>null if <see cref="_result"/> has the result, otherwise a <see cref="Task{TResult}"/> or a <see cref="IValueTaskSource{TResult}"/>.</summary> + internal readonly object _obj; /// <summary>The result to be used if the operation completed successfully synchronously.</summary> internal readonly TResult _result; + /// <summary>Flags providing additional details about the ValueTask's contents and behavior.</summary> + internal readonly ValueTaskFlags _flags; + /// <summary>Opaque value passed through to the <see cref="IValueTaskSource{TResult}"/>.</summary> + internal readonly short _token; + + // An instance created with the default ctor (a zero init'd struct) represents a synchronously, successfully completed operation + // with a result of default(TResult). - /// <summary>Initialize the <see cref="ValueTask{TResult}"/> with the result of the successful operation.</summary> + /// <summary>Initialize the <see cref="ValueTask{TResult}"/> with a <typeparamref name="TResult"/> result value.</summary> /// <param name="result">The result.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask(TResult result) { - _task = null; _result = result; + + _obj = null; + _flags = 0; + _token = 0; } - /// <summary> - /// Initialize the <see cref="ValueTask{TResult}"/> with a <see cref="Task{TResult}"/> that represents the operation. - /// </summary> + /// <summary>Initialize the <see cref="ValueTask{TResult}"/> with a <see cref="Task{TResult}"/> that represents the operation.</summary> /// <param name="task">The task.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask(Task<TResult> task) { if (task == null) @@ -75,95 +405,335 @@ namespace System.Threading.Tasks ThrowHelper.ThrowArgumentNullException(ExceptionArgument.task); } - _task = task; + _obj = task; + _result = default; + _flags = ValueTaskFlags.ObjectIsTask; + _token = 0; + } + + /// <summary>Initialize the <see cref="ValueTask{TResult}"/> with a <see cref="IValueTaskSource{TResult}"/> object that represents the operation.</summary> + /// <param name="source">The source.</param> + /// <param name="token">Opaque value passed through to the <see cref="IValueTaskSource"/>.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask(IValueTaskSource<TResult> source, short token) + { + if (source == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + _obj = source; + _token = token; + + _result = default; + _flags = 0; + } + + /// <summary>Non-verified initialization of the struct to the specified values.</summary> + /// <param name="obj">The object.</param> + /// <param name="result">The result.</param> + /// <param name="token">The token.</param> + /// <param name="flags">The flags.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ValueTask(object obj, TResult result, short token, ValueTaskFlags flags) + { + _obj = obj; + _result = result; + _token = token; + _flags = flags; + } + + /// <summary>Gets whether the contination should be scheduled to the current context.</summary> + internal bool ContinueOnCapturedContext + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (_flags & ValueTaskFlags.AvoidCapturedContext) == 0; + } + + /// <summary>Gets whether the object in the <see cref="_obj"/> field is a <see cref="Task{TResult}"/>.</summary> + internal bool ObjectIsTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (_flags & ValueTaskFlags.ObjectIsTask) != 0; + } + + /// <summary>Returns the <see cref="Task{TResult}"/> stored in <see cref="_obj"/>. This uses <see cref="Unsafe"/>.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Task<TResult> UnsafeGetTask() + { + Debug.Assert(ObjectIsTask); + Debug.Assert(_obj is Task<TResult>); + return Unsafe.As<Task<TResult>>(_obj); + } + + /// <summary>Returns the <see cref="IValueTaskSource{TResult}"/> stored in <see cref="_obj"/>. This uses <see cref="Unsafe"/>.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal IValueTaskSource<TResult> UnsafeGetValueTaskSource() + { + Debug.Assert(!ObjectIsTask); + Debug.Assert(_obj is IValueTaskSource<TResult>); + return Unsafe.As<IValueTaskSource<TResult>>(_obj); } /// <summary>Returns the hash code for this instance.</summary> public override int GetHashCode() => - _task != null ? _task.GetHashCode() : + _obj != null ? _obj.GetHashCode() : _result != null ? _result.GetHashCode() : 0; /// <summary>Returns a value indicating whether this value is equal to a specified <see cref="object"/>.</summary> public override bool Equals(object obj) => - obj is ValueTask<TResult> && + obj is ValueTask<TResult> && Equals((ValueTask<TResult>)obj); /// <summary>Returns a value indicating whether this value is equal to a specified <see cref="ValueTask{TResult}"/> value.</summary> public bool Equals(ValueTask<TResult> other) => - _task != null || other._task != null ? - _task == other._task : + _obj != null || other._obj != null ? + _obj == other._obj && _token == other._token : EqualityComparer<TResult>.Default.Equals(_result, other._result); /// <summary>Returns a value indicating whether two <see cref="ValueTask{TResult}"/> values are equal.</summary> - public static bool operator==(ValueTask<TResult> left, ValueTask<TResult> right) => + public static bool operator ==(ValueTask<TResult> left, ValueTask<TResult> right) => left.Equals(right); /// <summary>Returns a value indicating whether two <see cref="ValueTask{TResult}"/> values are not equal.</summary> - public static bool operator!=(ValueTask<TResult> left, ValueTask<TResult> right) => + public static bool operator !=(ValueTask<TResult> left, ValueTask<TResult> right) => !left.Equals(right); /// <summary> - /// Gets a <see cref="Task{TResult}"/> object to represent this ValueTask. It will - /// either return the wrapped task object if one exists, or it'll manufacture a new - /// task object to represent the result. + /// Gets a <see cref="Task{TResult}"/> object to represent this ValueTask. /// </summary> + /// <remarks> + /// It will either return the wrapped task object if one exists, or it'll + /// manufacture a new task object to represent the result. + /// </remarks> public Task<TResult> AsTask() => - // Return the task if we were constructed from one, otherwise manufacture one. We don't - // cache the generated task into _task as it would end up changing both equality comparison - // and the hash code we generate in GetHashCode. - _task ?? + _obj == null ? #if netstandard - Task.FromResult(_result); + Task.FromResult(_result) : #else - AsyncTaskMethodBuilder<TResult>.GetTaskForResult(_result); + AsyncTaskMethodBuilder<TResult>.GetTaskForResult(_result) : #endif + ObjectIsTask ? UnsafeGetTask() : + GetTaskForValueTaskSource(); - internal Task<TResult> AsTaskExpectNonNull() => - // Return the task if we were constructed from one, otherwise manufacture one. - // Unlike AsTask(), this method is called only when we expect _task to be non-null, - // and thus we don't want GetTaskForResult inlined. - _task ?? GetTaskForResultNoInlining(); + /// <summary>Gets a <see cref="ValueTask{TResult}"/> that may be used at any point in the future.</summary> + public ValueTask<TResult> Preserve() => _obj == null ? this : new ValueTask<TResult>(AsTask()); - [MethodImpl(MethodImplOptions.NoInlining)] - private Task<TResult> GetTaskForResultNoInlining() => + /// <summary>Creates a <see cref="Task{TResult}"/> to represent the <see cref="IValueTaskSource{TResult}"/>.</summary> + private Task<TResult> GetTaskForValueTaskSource() + { + IValueTaskSource<TResult> t = UnsafeGetValueTaskSource(); + ValueTaskSourceStatus status = t.GetStatus(_token); + if (status != ValueTaskSourceStatus.Pending) + { + try + { + // Get the result of the operation and return a task for it. + // If any exception occurred, propagate it + return #if netstandard - Task.FromResult(_result); + Task.FromResult(t.GetResult(_token)); #else - AsyncTaskMethodBuilder<TResult>.GetTaskForResult(_result); + AsyncTaskMethodBuilder<TResult>.GetTaskForResult(t.GetResult(_token)); #endif + // If status is Faulted or Canceled, GetResult should throw. But + // we can't guarantee every implementation will do the "right thing". + // If it doesn't throw, we just treat that as success and ignore + // the status. + } + catch (Exception exc) + { + if (status == ValueTaskSourceStatus.Canceled) + { +#if netstandard + var tcs = new TaskCompletionSource<TResult>(); + tcs.TrySetCanceled(); + return tcs.Task; +#else + if (exc is OperationCanceledException oce) + { + var task = new Task<TResult>(); + task.TrySetCanceled(oce.CancellationToken, oce); + return task; + } + else + { + return Task.FromCanceled<TResult>(new CancellationToken(true)); + } +#endif + } + else + { +#if netstandard + var tcs = new TaskCompletionSource<TResult>(); + tcs.TrySetException(exc); + return tcs.Task; +#else + return Task.FromException<TResult>(exc); +#endif + } + } + } + + var m = new ValueTaskSourceTask(t, _token); + return +#if netstandard + m.Task; +#else + m; +#endif + } + + /// <summary>Type used to create a <see cref="Task{TResult}"/> to represent a <see cref="IValueTaskSource{TResult}"/>.</summary> + private sealed class ValueTaskSourceTask : +#if netstandard + TaskCompletionSource<TResult> +#else + Task<TResult> +#endif + { + private static readonly Action<object> s_completionAction = state => + { + if (!(state is ValueTaskSourceTask vtst) || + !(vtst._source is IValueTaskSource<TResult> source)) + { + // This could only happen if the IValueTaskSource<TResult> passed the wrong state + // or if this callback were invoked multiple times such that the state + // was previously nulled out. + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state); + return; + } + + vtst._source = null; + ValueTaskSourceStatus status = source.GetStatus(vtst._token); + try + { + vtst.TrySetResult(source.GetResult(vtst._token)); + } + catch (Exception exc) + { + if (status == ValueTaskSourceStatus.Canceled) + { +#if netstandard + vtst.TrySetCanceled(); +#else + if (exc is OperationCanceledException oce) + { + vtst.TrySetCanceled(oce.CancellationToken, oce); + } + else + { + vtst.TrySetCanceled(new CancellationToken(true)); + } +#endif + } + else + { + vtst.TrySetException(exc); + } + } + }; + + /// <summary>The associated <see cref="IValueTaskSource"/>.</summary> + private IValueTaskSource<TResult> _source; + /// <summary>The token to pass through to operations on <see cref="_source"/></summary> + private readonly short _token; + + public ValueTaskSourceTask(IValueTaskSource<TResult> source, short token) + { + _source = source; + _token = token; + source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None); + } + } + /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a completed operation.</summary> - public bool IsCompleted => _task == null || _task.IsCompleted; + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _obj == null || (ObjectIsTask ? UnsafeGetTask().IsCompleted : UnsafeGetValueTaskSource().GetStatus(_token) != ValueTaskSourceStatus.Pending); + } /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a successfully completed operation.</summary> - public bool IsCompletedSuccessfully => - _task == null || + public bool IsCompletedSuccessfully + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => + _obj == null || + (ObjectIsTask ? #if netstandard - _task.Status == TaskStatus.RanToCompletion; + UnsafeGetTask().Status == TaskStatus.RanToCompletion : #else - _task.IsCompletedSuccessfully; + UnsafeGetTask().IsCompletedSuccessfully : #endif + UnsafeGetValueTaskSource().GetStatus(_token) == ValueTaskSourceStatus.Succeeded); + } /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a failed operation.</summary> - public bool IsFaulted => _task != null && _task.IsFaulted; + public bool IsFaulted + { + get => + _obj != null && + (ObjectIsTask ? UnsafeGetTask().IsFaulted : UnsafeGetValueTaskSource().GetStatus(_token) == ValueTaskSourceStatus.Faulted); + } /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a canceled operation.</summary> - public bool IsCanceled => _task != null && _task.IsCanceled; + /// <remarks> + /// If the <see cref="ValueTask{TResult}"/> is backed by a result or by a <see cref="IValueTaskSource{TResult}"/>, + /// this will always return false. If it's backed by a <see cref="Task"/>, it'll return the + /// value of the task's <see cref="Task.IsCanceled"/> property. + /// </remarks> + public bool IsCanceled + { + get => + _obj != null && + (ObjectIsTask ? UnsafeGetTask().IsCanceled : UnsafeGetValueTaskSource().GetStatus(_token) == ValueTaskSourceStatus.Canceled); + } /// <summary>Gets the result.</summary> - public TResult Result => _task == null ? _result : _task.GetAwaiter().GetResult(); + public TResult Result + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (_obj == null) + { + return _result; + } - /// <summary>Gets an awaiter for this value.</summary> + if (ObjectIsTask) + { +#if netstandard + return UnsafeGetTask().GetAwaiter().GetResult(); +#else + Task<TResult> t = UnsafeGetTask(); + TaskAwaiter.ValidateEnd(t); + return t.ResultOnSuccess; +#endif + } + + return UnsafeGetValueTaskSource().GetResult(_token); + } + } + + /// <summary>Gets an awaiter for this <see cref="ValueTask{TResult}"/>.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTaskAwaiter<TResult> GetAwaiter() => new ValueTaskAwaiter<TResult>(this); - /// <summary>Configures an awaiter for this value.</summary> + /// <summary>Configures an awaiter for this <see cref="ValueTask{TResult}"/>.</summary> /// <param name="continueOnCapturedContext"> /// true to attempt to marshal the continuation back to the captured context; otherwise, false. /// </param> - public ConfiguredValueTaskAwaitable<TResult> ConfigureAwait(bool continueOnCapturedContext) => - new ConfiguredValueTaskAwaitable<TResult>(this, continueOnCapturedContext); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConfiguredValueTaskAwaitable<TResult> ConfigureAwait(bool continueOnCapturedContext) + { + // TODO: Simplify once https://github.com/dotnet/coreclr/pull/16138 is fixed. + bool avoidCapture = !continueOnCapturedContext; + return new ConfiguredValueTaskAwaitable<TResult>(new ValueTask<TResult>(_obj, _result, _token, _flags | Unsafe.As<bool, ValueTaskFlags>(ref avoidCapture))); + } /// <summary>Gets a string-representation of this <see cref="ValueTask{TResult}"/>.</summary> public override string ToString() @@ -180,4 +750,26 @@ namespace System.Threading.Tasks return string.Empty; } } + + /// <summary>Internal flags used in the implementation of <see cref="ValueTask"/> and <see cref="ValueTask{TResult}"/>.</summary> + [Flags] + internal enum ValueTaskFlags : byte + { + /// <summary> + /// Indicates that context (e.g. SynchronizationContext) should not be captured when adding + /// a continuation. + /// </summary> + /// <remarks> + /// The value here must be 0x1, to match the value of a true Boolean reinterpreted as a byte. + /// This only has meaning when awaiting a ValueTask, with ConfigureAwait creating a new + /// ValueTask setting or not setting this flag appropriately. + /// </remarks> + AvoidCapturedContext = 0x1, + + /// <summary> + /// Indicates that the ValueTask's object field stores a Task. This is used to avoid + /// a type check on whatever is stored in the object field. + /// </summary> + ObjectIsTask = 0x2 + } } diff --git a/src/System.Private.CoreLib/shared/System/Type.cs b/src/System.Private.CoreLib/shared/System/Type.cs index a0d219ddd..b57baa869 100644 --- a/src/System.Private.CoreLib/shared/System/Type.cs +++ b/src/System.Private.CoreLib/shared/System/Type.cs @@ -349,7 +349,7 @@ namespace System public static Type MakeGenericMethodParameter(int position) { if (position < 0) - throw new ArgumentException(SR.ArgumentOutOfRange_MustBeNonNegNum, nameof(position)); + throw new ArgumentException(SR.ArgumentOutOfRange_NeedNonNegNum, nameof(position)); return new SignatureGenericMethodParameterType(position); } diff --git a/src/System.Private.CoreLib/shared/System/Version.cs b/src/System.Private.CoreLib/shared/System/Version.cs index 9e4cefcd6..fe086be51 100644 --- a/src/System.Private.CoreLib/shared/System/Version.cs +++ b/src/System.Private.CoreLib/shared/System/Version.cs @@ -333,13 +333,15 @@ namespace System // Find the ends of the optional minor and build portions. // We musn't have any separators after build. int buildEnd = -1; - int minorEnd = input.IndexOf('.', majorEnd + 1); + int minorEnd = input.Slice(majorEnd + 1).IndexOf('.'); if (minorEnd != -1) { - buildEnd = input.IndexOf('.', minorEnd + 1); + minorEnd += (majorEnd + 1); + buildEnd = input.Slice(minorEnd + 1).IndexOf('.'); if (buildEnd != -1) { - if (input.IndexOf('.', buildEnd + 1) != -1) + buildEnd += (minorEnd + 1); + if (input.Slice(buildEnd + 1).IndexOf('.') != -1) { if (throwOnFailure) throw new ArgumentException(SR.Arg_VersionString, nameof(input)); return null; @@ -347,10 +349,10 @@ namespace System } } - int major, minor, build, revision; + int minor, build, revision; // Parse the major version - if (!TryParseComponent(input.Slice(0, majorEnd), nameof(input), throwOnFailure, out major)) + if (!TryParseComponent(input.Slice(0, majorEnd), nameof(input), throwOnFailure, out int major)) { return null; } diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.Unix.cs b/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.Unix.cs index 7b9d380e7..4d4406027 100644 --- a/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.Unix.cs +++ b/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.Unix.cs @@ -159,5 +159,17 @@ namespace Internal.Runtime.Augments { throw new PlatformNotSupportedException(); } + + private static int ComputeCurrentProcessorId() + { + int processorId = Interop.Sys.SchedGetCpu(); + + // sched_getcpu doesn't exist on all platforms. On those it doesn't exist on, the shim + // returns -1. As a fallback in that case and to spread the threads across the buckets + // by default, we use the current managed thread ID as a proxy. + if (processorId < 0) processorId = Environment.CurrentManagedThreadId; + + return processorId; + } } } diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.Windows.cs b/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.Windows.cs index 6d0e937dc..b36666bc9 100644 --- a/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.Windows.cs +++ b/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.Windows.cs @@ -386,5 +386,7 @@ namespace Internal.Runtime.Augments STA, MTA } + + private static int ComputeCurrentProcessorId() => (int)Interop.mincore.GetCurrentProcessorNumber(); } } diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.cs b/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.cs index b21d9ced8..7bfce74e9 100644 --- a/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.cs +++ b/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.cs @@ -7,6 +7,7 @@ using System; using System.Diagnostics; using System.Globalization; using System.Runtime; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; @@ -491,5 +492,42 @@ namespace Internal.Runtime.Augments thread.SetThreadStateBit(ThreadState.Stopped); } } + + // The upper bits of t_currentProcessorIdCache are the currentProcessorId. The lower bits of + // the t_currentProcessorIdCache are counting down to get it periodically refreshed. + // TODO: Consider flushing the currentProcessorIdCache on Wait operations or similar + // actions that are likely to result in changing the executing core + [ThreadStatic] + private static int t_currentProcessorIdCache; + + private const int ProcessorIdCacheShift = 16; + private const int ProcessorIdCacheCountDownMask = (1 << ProcessorIdCacheShift) - 1; + private const int ProcessorIdRefreshRate = 5000; + + private static int RefreshCurrentProcessorId() + { + int currentProcessorId = ComputeCurrentProcessorId(); + + // Add offset to make it clear that it is not guaranteed to be 0-based processor number + currentProcessorId += 100; + + Debug.Assert(ProcessorIdRefreshRate <= ProcessorIdCacheCountDownMask); + + // Mask with Int32.MaxValue to ensure the execution Id is not negative + t_currentProcessorIdCache = ((currentProcessorId << ProcessorIdCacheShift) & Int32.MaxValue) + ProcessorIdRefreshRate; + + return currentProcessorId; + } + + // Cached processor id used as a hint for which per-core stack to access. It is periodically + // refreshed to trail the actual thread core affinity. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetCurrentProcessorId() + { + int currentProcessorIdCache = t_currentProcessorIdCache--; + if ((currentProcessorIdCache & ProcessorIdCacheCountDownMask) == 0) + return RefreshCurrentProcessorId(); + return (currentProcessorIdCache >> ProcessorIdCacheShift); + } } } diff --git a/src/System.Private.CoreLib/src/Resources/Strings.resx b/src/System.Private.CoreLib/src/Resources/Strings.resx index 2eedd405c..76ceb5883 100644 --- a/src/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/System.Private.CoreLib/src/Resources/Strings.resx @@ -798,9 +798,6 @@ <data name="ArgumentNull_Waithandles" xml:space="preserve"> <value>The waitHandles parameter cannot be null.</value> </data> - <data name="ArgumentNull_WithParamName" xml:space="preserve"> - <value>Parameter '{0}' cannot be null.</value> - </data> <data name="ArgumentOutOfRange_AddValue" xml:space="preserve"> <value>Value to add was out of range.</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 bd1371f69..58f19377e 100644 --- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -223,8 +223,6 @@ <Compile Include="System\Environment.cs" /> <Compile Include="System\GC.cs" /> <Compile Include="System\Globalization\CultureInfo.cs" /> - <Compile Include="System\Guid.Windows.cs" Condition="'$(TargetsWindows)'=='true'" /> - <Compile Include="System\Guid.Unix.cs" Condition="'$(TargetsUnix)'=='true'" /> <Compile Include="System\Helpers.cs" /> <Compile Include="System\InsufficientMemoryException.cs" /> <Compile Include="System\Marvin.cs" /> @@ -273,6 +271,7 @@ <Compile Include="System\Runtime\Serialization\SerializationInfo.cs" /> <Compile Include="System\String.cs" /> <Compile Include="System\String.Comparison.cs" /> + <Compile Include="System\String.CoreRT.cs" /> <Compile Include="System\String.Intern.cs" /> <Compile Include="System\Array.cs" /> <Compile Include="System\Array.CoreRT.cs" /> @@ -529,9 +528,6 @@ <Compile Condition="'$(EnableWinRT)' != 'true'" Include="..\..\Common\src\Interop\Windows\mincore\Interop.GetSystemDirectory.cs"> <Link>Interop\Windows\mincore\Interop.GetSystemDirectory.cs</Link> </Compile> - <Compile Include="..\..\Common\src\Interop\Windows\mincore\Interop.CoCreateGuid.cs"> - <Link>Interop\Windows\mincore\Interop.CoCreateGuid.cs</Link> - </Compile> <Compile Include="..\..\Common\src\Interop\Windows\mincore\Interop.QueryUnbiasedInterruptTime.cs"> <Link>Interop\Windows\mincore\Interop.QueryUnbiasedInterruptTime.cs</Link> </Compile> diff --git a/src/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/System.Private.CoreLib/src/System/Environment.Unix.cs index c45d54bd8..af2e5517a 100644 --- a/src/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -20,18 +20,6 @@ namespace System } } - private static int ComputeExecutionId() - { - int executionId = Interop.Sys.SchedGetCpu(); - - // sched_getcpu doesn't exist on all platforms. On those it doesn't exist on, the shim - // returns -1. As a fallback in that case and to spread the threads across the buckets - // by default, we use the current managed thread ID as a proxy. - if (executionId < 0) executionId = Environment.CurrentManagedThreadId; - - return executionId; - } - #if DEBUG [Obsolete("ExpandEnvironmentVariables() only called on Windows so not implemented on Unix.")] public static string ExpandEnvironmentVariables(string name) diff --git a/src/System.Private.CoreLib/src/System/Environment.Windows.cs b/src/System.Private.CoreLib/src/System/Environment.Windows.cs index f5fca35ab..71c9e06a4 100644 --- a/src/System.Private.CoreLib/src/System/Environment.Windows.cs +++ b/src/System.Private.CoreLib/src/System/Environment.Windows.cs @@ -12,8 +12,6 @@ namespace System internal static long TickCount64 => (long)Interop.mincore.GetTickCount64(); - private static int ComputeExecutionId() => (int)Interop.mincore.GetCurrentProcessorNumber(); - public static string ExpandEnvironmentVariables(string name) { if (name == null) diff --git a/src/System.Private.CoreLib/src/System/Environment.cs b/src/System.Private.CoreLib/src/System/Environment.cs index c1d4ac171..7dc4ff715 100644 --- a/src/System.Private.CoreLib/src/System/Environment.cs +++ b/src/System.Private.CoreLib/src/System/Environment.cs @@ -82,43 +82,6 @@ namespace System } } - // The upper bits of t_executionIdCache are the executionId. The lower bits of - // the t_executionIdCache are counting down to get it periodically refreshed. - // TODO: Consider flushing the executionIdCache on Wait operations or similar - // actions that are likely to result in changing the executing core - [ThreadStatic] - private static int t_executionIdCache; - - private const int ExecutionIdCacheShift = 16; - private const int ExecutionIdCacheCountDownMask = (1 << ExecutionIdCacheShift) - 1; - private const int ExecutionIdRefreshRate = 5000; - - private static int RefreshExecutionId() - { - int executionId = ComputeExecutionId(); - - Debug.Assert(ExecutionIdRefreshRate <= ExecutionIdCacheCountDownMask); - - // Mask with Int32.MaxValue to ensure the execution Id is not negative - t_executionIdCache = ((executionId << ExecutionIdCacheShift) & Int32.MaxValue) + ExecutionIdRefreshRate; - - return executionId; - } - - // Cached processor number used as a hint for which per-core stack to access. It is periodically - // refreshed to trail the actual thread core affinity. - internal static int CurrentExecutionId - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - int executionIdCache = t_executionIdCache--; - if ((executionIdCache & ExecutionIdCacheCountDownMask) == 0) - return RefreshExecutionId(); - return (executionIdCache >> ExecutionIdCacheShift); - } - } - public static bool HasShutdownStarted { get diff --git a/src/System.Private.CoreLib/src/System/Guid.Unix.cs b/src/System.Private.CoreLib/src/System/Guid.Unix.cs deleted file mode 100644 index 326362d04..000000000 --- a/src/System.Private.CoreLib/src/System/Guid.Unix.cs +++ /dev/null @@ -1,24 +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 -{ - partial struct Guid - { - // This will create a new guid. Since we've now decided that constructors should 0-init, - // we need a method that allows users to create a guid. - public static Guid NewGuid() - { - // CoCreateGuid should never return Guid.Empty, since it attempts to maintain some - // uniqueness guarantees. It should also never return a known GUID, but it's unclear - // how extensively it checks for known values. - - Interop.Sys.CreateGuid(out Guid g); - return g; - } - } -} diff --git a/src/System.Private.CoreLib/src/System/IO/Stream.cs b/src/System.Private.CoreLib/src/System/IO/Stream.cs index b4f8ab30d..c307ec91e 100644 --- a/src/System.Private.CoreLib/src/System/IO/Stream.cs +++ b/src/System.Private.CoreLib/src/System/IO/Stream.cs @@ -242,7 +242,7 @@ namespace System.IO public virtual ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default(CancellationToken)) { - if (destination.TryGetArray(out ArraySegment<byte> array)) + if (MemoryMarshal.TryGetArray(destination, out ArraySegment<byte> array)) { return new ValueTask<int>(ReadAsync(array.Array, array.Offset, array.Count, cancellationToken)); } @@ -316,17 +316,17 @@ namespace System.IO buffer, offset, count, this); } - public virtual Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) + public virtual ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) { if (MemoryMarshal.TryGetArray(source, out ArraySegment<byte> array)) { - return WriteAsync(array.Array, array.Offset, array.Count, cancellationToken); + return new ValueTask(WriteAsync(array.Array, array.Offset, array.Count, cancellationToken)); } else { byte[] buffer = ArrayPool<byte>.Shared.Rent(source.Length); source.Span.CopyTo(buffer); - return FinishWriteAsync(WriteAsync(buffer, 0, source.Length, cancellationToken), buffer); + return new ValueTask(FinishWriteAsync(WriteAsync(buffer, 0, source.Length, cancellationToken), buffer)); async Task FinishWriteAsync(Task writeTask, byte[] localBuffer) { @@ -559,7 +559,7 @@ namespace System.IO cancellationToken.ThrowIfCancellationRequested(); } - public override async Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) + public override async ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); } diff --git a/src/System.Private.CoreLib/src/System/String.Comparison.cs b/src/System.Private.CoreLib/src/System/String.Comparison.cs index 914fa6aa1..357c305d9 100644 --- a/src/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/System.Private.CoreLib/src/System/String.Comparison.cs @@ -166,7 +166,7 @@ namespace System // // Common worker for the various Equality methods. The caller must have already ensured that // both strings are non-null and that their lengths are equal. Ther caller should also have - // done the Object.ReferenceEquals() fastpath check as we won't repeat it here. + // done the object.ReferenceEquals() fastpath check as we won't repeat it here. // private static unsafe bool EqualsHelper(String strA, String strB) { @@ -436,19 +436,19 @@ namespace System { if (object.ReferenceEquals(strA, strB)) { - StringSpanHelpers.CheckStringComparison(comparisonType); + CheckStringComparison(comparisonType); return 0; } // They can't both be null at this point. if (strA == null) { - StringSpanHelpers.CheckStringComparison(comparisonType); + CheckStringComparison(comparisonType); return -1; } if (strB == null) { - StringSpanHelpers.CheckStringComparison(comparisonType); + CheckStringComparison(comparisonType); return 1; } @@ -601,7 +601,7 @@ namespace System public static int Compare(String strA, int indexA, String strB, int indexB, int length, StringComparison comparisonType) { - StringSpanHelpers.CheckStringComparison(comparisonType); + CheckStringComparison(comparisonType); if (strA == null || strB == null) { @@ -614,7 +614,6 @@ namespace System return strA == null ? -1 : 1; } - // @TODO: Spec#: Figure out what to do here with the return statement above. if (length < 0) { throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength); @@ -805,7 +804,7 @@ namespace System return string.Compare(this, strB, StringComparison.CurrentCulture); } - // Determines whether a specified string is a suffix of the the current instance. + // Determines whether a specified string is a suffix of the current instance. // // The case-sensitive and culture-sensitive option is set by options, // and the default culture is used. @@ -825,13 +824,13 @@ namespace System if ((Object)this == (Object)value) { - StringSpanHelpers.CheckStringComparison(comparisonType); + CheckStringComparison(comparisonType); return true; } if (value.Length == 0) { - StringSpanHelpers.CheckStringComparison(comparisonType); + CheckStringComparison(comparisonType); return true; } @@ -872,12 +871,7 @@ namespace System return true; } - CultureInfo referenceCulture; - if (culture == null) - referenceCulture = CultureInfo.CurrentCulture; - else - referenceCulture = culture; - + CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture; return referenceCulture.CompareInfo.IsSuffix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); } @@ -891,10 +885,10 @@ namespace System public override bool Equals(Object obj) { - if (Object.ReferenceEquals(this, obj)) + if (object.ReferenceEquals(this, obj)) return true; - String str = obj as String; + string str = obj as string; if (str == null) return false; @@ -909,7 +903,7 @@ namespace System public bool Equals(String value) { - if (Object.ReferenceEquals(this, value)) + if (object.ReferenceEquals(this, value)) return true; // NOTE: No need to worry about casting to object here. @@ -930,13 +924,13 @@ namespace System { if ((Object)this == (Object)value) { - StringSpanHelpers.CheckStringComparison(comparisonType); + CheckStringComparison(comparisonType); return true; } if ((Object)value == null) { - StringSpanHelpers.CheckStringComparison(comparisonType); + CheckStringComparison(comparisonType); return false; } @@ -993,13 +987,13 @@ namespace System { if ((Object)a == (Object)b) { - StringSpanHelpers.CheckStringComparison(comparisonType); + CheckStringComparison(comparisonType); return true; } if ((Object)a == null || (Object)b == null) { - StringSpanHelpers.CheckStringComparison(comparisonType); + CheckStringComparison(comparisonType); return false; } @@ -1037,7 +1031,7 @@ namespace System public static bool operator ==(String a, String b) { - if (Object.ReferenceEquals(a, b)) + if (object.ReferenceEquals(a, b)) return true; if (a == null || b == null || a.Length != b.Length) return false; @@ -1046,7 +1040,7 @@ namespace System public static bool operator !=(String a, String b) { - if (Object.ReferenceEquals(a, b)) + if (object.ReferenceEquals(a, b)) return false; if (a == null || b == null || a.Length != b.Length) return true; @@ -1135,13 +1129,13 @@ namespace System if ((Object)this == (Object)value) { - StringSpanHelpers.CheckStringComparison(comparisonType); + CheckStringComparison(comparisonType); return true; } if (value.Length == 0) { - StringSpanHelpers.CheckStringComparison(comparisonType); + CheckStringComparison(comparisonType); return true; } @@ -1192,15 +1186,19 @@ namespace System return true; } - CultureInfo referenceCulture; - if (culture == null) - referenceCulture = CultureInfo.CurrentCulture; - else - referenceCulture = culture; - + CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture; return referenceCulture.CompareInfo.IsPrefix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); } public bool StartsWith(char value) => Length != 0 && _firstChar == value; + + internal static void CheckStringComparison(StringComparison comparisonType) + { + // Single comparison to check if comparisonType is within [CurrentCulture .. OrdinalIgnoreCase] + if ((uint)(comparisonType - StringComparison.CurrentCulture) > (StringComparison.OrdinalIgnoreCase - StringComparison.CurrentCulture)) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.NotSupported_StringComparison, ExceptionArgument.comparisonType); + } + } } } diff --git a/src/System.Private.CoreLib/src/System/String.CoreRT.cs b/src/System.Private.CoreLib/src/System/String.CoreRT.cs new file mode 100644 index 000000000..a892006d9 --- /dev/null +++ b/src/System.Private.CoreLib/src/System/String.CoreRT.cs @@ -0,0 +1,85 @@ +// 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; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; + +using Internal.Runtime.CompilerServices; + +namespace System +{ + [StructLayout(LayoutKind.Sequential)] + [System.Runtime.CompilerServices.EagerStaticClassConstructionAttribute] + [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] + public partial class String + { +#if BIT64 + private const int POINTER_SIZE = 8; +#else + private const int POINTER_SIZE = 4; +#endif + // m_pEEType + _stringLength + internal const int FIRST_CHAR_OFFSET = POINTER_SIZE + sizeof(int); + + // CS0169: The private field '{blah}' is never used + // CS0649: Field '{blah}' is never assigned to, and will always have its default value +#pragma warning disable 169, 649 + +#if PROJECTN + [Bound] +#endif + // WARNING: We allow diagnostic tools to directly inspect these two members (_stringLength, _firstChar) + // See https://github.com/dotnet/corert/blob/master/Documentation/design-docs/diagnostics/diagnostics-tools-contract.md for more details. + // Please do not change the type, the name, or the semantic usage of this member without understanding the implication for tools. + // Get in touch with the diagnostics team if you have questions. + [NonSerialized] + private int _stringLength; + [NonSerialized] + private char _firstChar; + +#pragma warning restore + + public static readonly String Empty = ""; + + // Gets the character at a specified position. + // + // Spec#: Apply the precondition here using a contract assembly. Potential perf issue. + [System.Runtime.CompilerServices.IndexerName("Chars")] + public unsafe char this[int index] + { +#if PROJECTN + [BoundsChecking] + get + { + return Unsafe.Add(ref _firstChar, index); + } +#else + [Intrinsic] + get + { + if ((uint)index >= _stringLength) + ThrowHelper.ThrowIndexOutOfRangeException(); + return Unsafe.Add(ref _firstChar, index); + } +#endif + } + + internal static String FastAllocateString(int length) + { + // We allocate one extra char as an interop convenience so that our strings are null- + // terminated, however, we don't pass the extra +1 to the string allocation because the base + // size of this object includes the _firstChar field. + string newStr = RuntimeImports.RhNewString(EETypePtr.EETypePtrOf<string>(), length); + Debug.Assert(newStr._stringLength == length); + return newStr; + } + } +} diff --git a/src/System.Private.CoreLib/src/System/String.cs b/src/System.Private.CoreLib/src/System/String.cs index 076bd02a8..6c0f7532b 100644 --- a/src/System.Private.CoreLib/src/System/String.cs +++ b/src/System.Private.CoreLib/src/System/String.cs @@ -74,38 +74,9 @@ namespace System // constructed itself depends on this class also being eagerly constructed. Plus, it's nice to have this // eagerly constructed to avoid the cost of defered ctors. I can't imagine any app that doesn't use string // - [StructLayout(LayoutKind.Sequential)] - [System.Runtime.CompilerServices.EagerStaticClassConstructionAttribute] [Serializable] - [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] public sealed partial class String : IComparable, IEnumerable, IEnumerable<char>, IComparable<String>, IEquatable<String>, IConvertible, ICloneable { -#if BIT64 - private const int POINTER_SIZE = 8; -#else - private const int POINTER_SIZE = 4; -#endif - // m_pEEType + _stringLength - internal const int FIRST_CHAR_OFFSET = POINTER_SIZE + sizeof(int); - - // CS0169: The private field '{blah}' is never used - // CS0649: Field '{blah}' is never assigned to, and will always have its default value -#pragma warning disable 169, 649 - -#if PROJECTN - [Bound] -#endif - // WARNING: We allow diagnostic tools to directly inspect these two members (_stringLength, _firstChar) - // See https://github.com/dotnet/corert/blob/master/Documentation/design-docs/diagnostics/diagnostics-tools-contract.md for more details. - // Please do not change the type, the name, or the semantic usage of this member without understanding the implication for tools. - // Get in touch with the diagnostics team if you have questions. - [NonSerialized] - private int _stringLength; - [NonSerialized] - private char _firstChar; - -#pragma warning restore - // String constructors // These are special. the implementation methods for these have a different signature from the // declared constructors. @@ -464,31 +435,6 @@ namespace System return result; } - public static readonly String Empty = ""; - - // Gets the character at a specified position. - // - // Spec#: Apply the precondition here using a contract assembly. Potential perf issue. - [System.Runtime.CompilerServices.IndexerName("Chars")] - public unsafe char this[int index] - { -#if PROJECTN - [BoundsChecking] - get - { - return Unsafe.Add(ref _firstChar, index); - } -#else - [Intrinsic] - get - { - if ((uint)index >= _stringLength) - ThrowHelper.ThrowIndexOutOfRangeException(); - return Unsafe.Add(ref _firstChar, index); - } -#endif - } - // Converts a substring of this string to an array of characters. Copies the // characters of this string beginning at position sourceIndex and ending at // sourceIndex + count - 1 to the character array buffer, beginning @@ -630,16 +576,6 @@ namespace System return result; } - internal static String FastAllocateString(int length) - { - // We allocate one extra char as an interop convenience so that our strings are null- - // terminated, however, we don't pass the extra +1 to the string allocation because the base - // size of this object includes the _firstChar field. - string newStr = RuntimeImports.RhNewString(EETypePtr.EETypePtrOf<string>(), length); - Debug.Assert(newStr._stringLength == length); - return newStr; - } - internal static unsafe void wstrcpy(char* dmem, char* smem, int charCount) { Buffer.Memmove((byte*)dmem, (byte*)smem, ((uint)charCount) * 2); diff --git a/src/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Windows.cs b/src/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Windows.cs index 2800fc951..58bd76dae 100644 --- a/src/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Windows.cs +++ b/src/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Windows.cs @@ -63,7 +63,7 @@ namespace System.Threading { if (name == null) { - throw new ArgumentNullException(nameof(name), SR.ArgumentNull_WithParamName); + throw new ArgumentNullException(nameof(name)); } if (name.Length == 0) diff --git a/src/System.Private.CoreLib/src/System/Threading/Mutex.Windows.cs b/src/System.Private.CoreLib/src/System/Threading/Mutex.Windows.cs index ba5e86304..40b9ce758 100644 --- a/src/System.Private.CoreLib/src/System/Threading/Mutex.Windows.cs +++ b/src/System.Private.CoreLib/src/System/Threading/Mutex.Windows.cs @@ -45,7 +45,7 @@ namespace System.Threading { if (name == null) { - throw new ArgumentNullException(nameof(name), SR.ArgumentNull_WithParamName); + throw new ArgumentNullException(nameof(name)); } if (name.Length == 0) diff --git a/src/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/System.Private.CoreLib/src/System/ThrowHelper.cs index 03c70becd..8169b507c 100644 --- a/src/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -317,6 +317,14 @@ namespace System return "comparer"; case ExceptionArgument.comparable: return "comparable"; + case ExceptionArgument.source: + return "source"; + case ExceptionArgument.state: + return "state"; + case ExceptionArgument.length: + return "length"; + case ExceptionArgument.comparisonType: + return "comparisonType"; default: Debug.Fail("The enum value is not defined, please check the ExceptionArgument Enum."); return ""; @@ -365,6 +373,8 @@ namespace System return SR.TaskCompletionSourceT_TrySetException_NullException; case ExceptionResource.TaskCompletionSourceT_TrySetException_NoExceptions: return SR.TaskCompletionSourceT_TrySetException_NoExceptions; + case ExceptionResource.NotSupported_StringComparison: + return SR.NotSupported_StringComparison; default: Debug.Assert(false, "The enum value is not defined, please check the ExceptionResource Enum."); @@ -408,7 +418,11 @@ namespace System format, culture, comparer, - comparable + comparable, + source, + state, + length, + comparisonType, } // @@ -435,5 +449,6 @@ namespace System TaskT_TransitionToFinal_AlreadyCompleted, TaskCompletionSourceT_TrySetException_NullException, TaskCompletionSourceT_TrySetException_NoExceptions, + NotSupported_StringComparison, } } |