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

github.com/mono/corert.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/BuildIntegration/Microsoft.NETCore.Native.targets2
-rw-r--r--src/Common/src/TypeSystem/Common/TargetDetails.cs7
-rw-r--r--src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs7
-rw-r--r--src/ILCompiler.CppCodeGen/src/Compiler/DependencyAnalysis/CppCodegenNodeFactory.cs3
-rw-r--r--src/ILCompiler.CppCodeGen/src/Compiler/DependencyAnalysis/CppUnboxingStubNode.cs68
-rw-r--r--src/ILCompiler.CppCodeGen/src/CppCodeGen/CppWriter.cs44
-rw-r--r--src/ILCompiler.CppCodeGen/src/CppCodeGen/ILToCppImporter.cs2
-rw-r--r--src/ILCompiler.CppCodeGen/src/ILCompiler.CppCodeGen.csproj1
-rw-r--r--src/ILCompiler/src/Program.cs3
-rw-r--r--src/JitInterface/src/CorInfoTypes.cs2
-rw-r--r--src/JitInterface/src/ThunkGenerator/corinfo.h2
-rw-r--r--src/JitInterface/src/ThunkGenerator/corjitflags.h2
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Ole32/Interop.CoCreateGuid.cs (renamed from src/Common/src/Interop/Windows/mincore/Interop.CoCreateGuid.cs)6
-rw-r--r--src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems6
-rw-r--r--src/System.Private.CoreLib/shared/System/Boolean.cs9
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs6
-rw-r--r--src/System.Private.CoreLib/shared/System/Byte.cs4
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs248
-rw-r--r--src/System.Private.CoreLib/shared/System/DateTime.cs23
-rw-r--r--src/System.Private.CoreLib/shared/System/DateTimeOffset.cs23
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs39
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs2
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs4
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs11
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs28
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs31
-rw-r--r--src/System.Private.CoreLib/shared/System/Double.cs6
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Unix.cs7
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs40
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfo.cs4
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/DateTimeParse.cs20
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/TimeSpanParse.cs76
-rw-r--r--src/System.Private.CoreLib/shared/System/Guid.Unix.cs38
-rw-r--r--src/System.Private.CoreLib/shared/System/Guid.Windows.cs (renamed from src/System.Private.CoreLib/src/System/Guid.Windows.cs)8
-rw-r--r--src/System.Private.CoreLib/shared/System/Guid.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs25
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileStream.cs8
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Path.cs2
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs4
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathInternal.Unix.cs5
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs29
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathInternal.cs19
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/StreamReader.cs8
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs4
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/TextReader.cs5
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs10
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs2
-rw-r--r--src/System.Private.CoreLib/shared/System/Memory.cs52
-rw-r--r--src/System.Private.CoreLib/shared/System/MemoryDebugView.cs27
-rw-r--r--src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs96
-rw-r--r--src/System.Private.CoreLib/shared/System/MemoryExtensions.cs150
-rw-r--r--src/System.Private.CoreLib/shared/System/Number.Parsing.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs104
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs218
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TypeForwardedFromAttribute.cs3
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs188
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.Fast.cs84
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs24
-rw-r--r--src/System.Private.CoreLib/shared/System/Single.cs6
-rw-r--r--src/System.Private.CoreLib/shared/System/Span.NonGeneric.cs41
-rw-r--r--src/System.Private.CoreLib/shared/System/SpanHelpers.cs89
-rw-r--r--src/System.Private.CoreLib/shared/System/String.Searching.cs164
-rw-r--r--src/System.Private.CoreLib/shared/System/StringSpanHelpers.cs139
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/Decoder.cs10
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/Encoder.cs10
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/Encoding.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs2
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs82
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs754
-rw-r--r--src/System.Private.CoreLib/shared/System/Type.cs2
-rw-r--r--src/System.Private.CoreLib/shared/System/Version.cs12
-rw-r--r--src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.Unix.cs12
-rw-r--r--src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.Windows.cs2
-rw-r--r--src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.cs38
-rw-r--r--src/System.Private.CoreLib/src/Resources/Strings.resx3
-rw-r--r--src/System.Private.CoreLib/src/System.Private.CoreLib.csproj6
-rw-r--r--src/System.Private.CoreLib/src/System/Environment.Unix.cs12
-rw-r--r--src/System.Private.CoreLib/src/System/Environment.Windows.cs2
-rw-r--r--src/System.Private.CoreLib/src/System/Environment.cs37
-rw-r--r--src/System.Private.CoreLib/src/System/Guid.Unix.cs24
-rw-r--r--src/System.Private.CoreLib/src/System/IO/Stream.cs10
-rw-r--r--src/System.Private.CoreLib/src/System/String.Comparison.cs62
-rw-r--r--src/System.Private.CoreLib/src/System/String.CoreRT.cs85
-rw-r--r--src/System.Private.CoreLib/src/System/String.cs64
-rw-r--r--src/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Windows.cs2
-rw-r--r--src/System.Private.CoreLib/src/System/Threading/Mutex.Windows.cs2
-rw-r--r--src/System.Private.CoreLib/src/System/ThrowHelper.cs17
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) @&quot;$(NativeIntermediateOutputPath)lib.rsp&quot;" Condition="'$(OS)' == 'Windows_NT' and '$(NativeLib)' == 'Static' and '$(NativeCodeGen)' != 'wasm'" />
<PropertyGroup>
- <EmccArgs>&quot;$(NativeObject)&quot; -o &quot;$(NativeBinary)&quot; -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 </EmccArgs>
+ <EmccArgs>&quot;$(NativeObject)&quot; -o &quot;$(NativeBinary)&quot; -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 --emrun </EmccArgs>
<EmccArgs Condition="'$(Platform)'=='wasm'">$(EmccArgs) &quot;$(IlcPath)/sdk/libPortableRuntime.bc&quot; &quot;$(IlcPath)/sdk/libbootstrappercpp.bc&quot; </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 (&lt;0 or &gt;=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 (&lt;0 or &gt;=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 (&lt;0 or &gt;=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 (&lt;0 or &gt;=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 (&lt;0 or &gt;=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 (&lt;0 or &gt;=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 (&lt;0 or &gt;=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,
}
}