|
// 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.
// To quiet analyzers asking to change code from dotnet/runtime style to dotnet/roslyn style
// <auto-generated/>
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Microsoft.CodeAnalysis;
internal sealed partial class SolutionState
{
/// <summary>
/// String comparer for file paths that caches the last result of the comparison to avoid expensive rehashing of the
/// same string over and over again.
/// </summary>
private sealed class CachingFilePathComparer : IEqualityComparer<string>
{
/// <summary>
/// Note: this insensitive comparer is busted on many systems. But we do things this way for compat with the logic
/// we've had on windows since forever.
/// </summary>
private static readonly StringComparer s_comparer = StringComparer.OrdinalIgnoreCase;
public static readonly CachingFilePathComparer Instance = new();
/// <summary>
/// ThreadStatic so that gets its own copy it can safely read/write from, removing the need for expensive
/// contentious locks. The purpose of this type is to allow lookup of the same key across N dictionaries
/// efficiently from the same thread. So this accomplishes that purpose.
/// </summary>
[ThreadStatic]
private static (string? lastString, int lastHashCode) s_data;
private CachingFilePathComparer()
{
}
public bool Equals(string? x, string? y)
=> s_comparer.Equals(x, y);
public int GetHashCode([DisallowNull] string obj)
{
// SToub thinks this may be faster on NetFx as it will help the runtime with reading/writing from a single location.
ref var data = ref s_data;
if (ReferenceEquals(data.lastString, obj))
return data.lastHashCode;
// Hashing a different string than last time. Compute the hash and cache the value.
// Specialized impl of OrdinalIgnoreCase.GetHashCode that is faster for the common case of an all-ASCII
// string. Falls back to normal OrdinalIgnoreCase.GetHashCode for the uncommon case.
var hash = GetNonRandomizedHashCodeOrdinalIgnoreCase(obj);
data = (obj, hash);
return hash;
}
// From https://github.com/dotnet/runtime/blob/5aa9687e110faa19d1165ba680e52585a822464d/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs#L921
// We "normalize to lowercase" every char by ORing with 0x0020. This casts
// a very wide net because it will change, e.g., '^' to '~'. But that should
// be ok because we expect this to be very rare in practice. These are valid
// for both for big-endian and for little-endian.
private const uint NormalizeToLowercase = 0x0020_0020u;
private unsafe int GetNonRandomizedHashCodeOrdinalIgnoreCase(string obj)
{
uint hash1 = (5381 << 16) + 5381;
uint hash2 = hash1;
int length = obj.Length;
fixed (char* src = obj)
{
Debug.Assert(src[obj.Length] == '\0', "src[this.Length] == '\\0'");
Debug.Assert(((int)src) % 4 == 0, "Managed string should start at 4 bytes boundary");
uint* ptr = (uint*)src;
while (length > 2)
{
uint p0 = ptr[0];
uint p1 = ptr[1];
if (!AllCharsInUInt32AreAscii(p0 | p1))
{
goto NotAscii;
}
length -= 4;
hash1 = (RuntimeBitOperations.RotateLeft(hash1, 5) + hash1) ^ (p0 | NormalizeToLowercase);
hash2 = (RuntimeBitOperations.RotateLeft(hash2, 5) + hash2) ^ (p1 | NormalizeToLowercase);
ptr += 2;
}
if (length > 0)
{
uint p0 = ptr[0];
if (!AllCharsInUInt32AreAscii(p0))
{
goto NotAscii;
}
hash2 = (RuntimeBitOperations.RotateLeft(hash2, 5) + hash2) ^ (p0 | NormalizeToLowercase);
}
}
return (int)(hash1 + (hash2 * 1566083941));
NotAscii:
return s_comparer.GetHashCode(obj);
}
// From https://github.com/dotnet/runtime/blob/5aa9687e110faa19d1165ba680e52585a822464d/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/Utf16Utility.cs#L16.
/// <summary>
/// Returns true iff the UInt32 represents two ASCII UTF-16 characters in machine endianness.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool AllCharsInUInt32AreAscii(uint value)
{
return (value & ~0x007F_007Fu) == 0;
}
}
}
|