From d9a4b243ffcd6548f7fe44b9a12be8ea15cd9ddf Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 20 Mar 2024 02:53:25 +0100 Subject: [PATCH] Special-case Dictionary for string keys --- src/coreclr/jit/importercalls.cpp | 7 ++ src/coreclr/jit/morph.cpp | 5 +- .../System/Collections/Generic/Dictionary.cs | 66 +++++++++++++++++++ .../NonRandomizedStringEqualityComparer.cs | 2 +- 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index c9f852d94a0ad9..0f6c4e3ab5dd0b 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -3336,6 +3336,13 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, JITDUMP("\nExpanding RuntimeHelpers.IsKnownConstant to true early\n"); // We can also consider FTN_ADDR here } + else if (op1->IsHelperCall() && gtIsTypeHandleToRuntimeTypeHelper(op1->AsCall()) && + op1->AsCall()->gtArgs.GetArgByIndex(0)->GetNode()->IsIconHandle(GTF_ICON_CLASS_HDL)) + { + // We don't use gtIsTypeof here because it may return true for shared generic types + retNode = gtNewIconNode(1); + JITDUMP("\nExpanding RuntimeHelpers.IsKnownConstant(typeof(T)) to true early\n"); + } else if (opts.OptimizationDisabled()) { // It doesn't make sense to carry it as GT_INTRINSIC till Morph in Tier0 diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 1c9fc51d48567d..ea2ed1a5f689df 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -9660,9 +9660,10 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA assert(!optValnumCSE_phase); JITDUMP("\nExpanding RuntimeHelpers.IsKnownConstant to "); - if (op1->OperIsConst() || gtIsTypeof(op1)) + if (op1->OperIsConst()) { - // We're lucky to catch a constant here while importer was not + // We're lucky to catch a constant here while importer was not. + // typeof(T) is expected to be expanded into a nongc constant handle by now. JITDUMP("true\n"); DEBUG_DESTROY_NODE(tree, op1); tree = gtNewIconNode(1); diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs index 6007efe778ce21..620e31f57de42f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs @@ -394,7 +394,73 @@ public virtual void GetObjectData(SerializationInfo info, StreamingContext conte } } + internal ref TValue FindValue_OrdinalComparer(string? key) + { + if (key == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + + ref Entry entry = ref Unsafe.NullRef(); + if (_buckets != null) + { + Debug.Assert(_entries != null, "expected entries to be != null"); + Debug.Assert(_comparer is NonRandomizedStringEqualityComparer.OrdinalComparer); + + uint hashCode = (uint)key.GetNonRandomizedHashCode(); + int i = GetBucket(hashCode); + Entry[]? entries = _entries; + uint collisionCount = 0; + i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. + do + { + // Should be a while loop https://github.com/dotnet/runtime/issues/9422 + // Test in if to drop range check for following array access + if ((uint)i >= (uint)entries.Length) + { + goto ReturnNotFound; + } + + entry = ref entries[i]; + if (entry.hashCode == hashCode && string.Equals(Unsafe.As(ref entry.key), key)) + { + goto ReturnFound; + } + + i = entry.next; + + collisionCount++; + } while (collisionCount <= (uint)entries.Length); + + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + } + + goto ReturnNotFound; + + ReturnFound: + ref TValue value = ref entry.value; + Return: + return ref value; + ReturnNotFound: + value = ref Unsafe.NullRef(); + goto Return; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ref TValue FindValue(TKey key) + { + if (RuntimeHelpers.IsKnownConstant(typeof(TKey)) && typeof(TKey) == typeof(string) && + _comparer is NonRandomizedStringEqualityComparer.OrdinalComparer) + { + return ref FindValue_OrdinalComparer((string)(object)key); + } + return ref FindValueHelper(key); + } + + internal ref TValue FindValueHelper(TKey key) { if (key == null) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs index de01ba5bdc5ad9..cf1579cb7ec74a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs @@ -73,7 +73,7 @@ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext contex info.SetType(typeof(GenericEqualityComparer)); } - private sealed class OrdinalComparer : NonRandomizedStringEqualityComparer + internal sealed class OrdinalComparer : NonRandomizedStringEqualityComparer { internal OrdinalComparer(IEqualityComparer wrappedComparer) : base(wrappedComparer)