514 lines
11 KiB
C#
514 lines
11 KiB
C#
using System;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using Microsoft.CodeAnalysis.PooledObjects;
|
|
|
|
namespace Roslyn.Utilities;
|
|
|
|
internal class StringTable
|
|
{
|
|
private struct Entry
|
|
{
|
|
public int HashCode;
|
|
|
|
public string Text;
|
|
}
|
|
|
|
private const int LocalSizeBits = 11;
|
|
|
|
private const int LocalSize = 2048;
|
|
|
|
private const int LocalSizeMask = 2047;
|
|
|
|
private const int SharedSizeBits = 16;
|
|
|
|
private const int SharedSize = 65536;
|
|
|
|
private const int SharedSizeMask = 65535;
|
|
|
|
private const int SharedBucketBits = 4;
|
|
|
|
private const int SharedBucketSize = 16;
|
|
|
|
private const int SharedBucketSizeMask = 15;
|
|
|
|
private readonly Entry[] _localTable = new Entry[2048];
|
|
|
|
private static readonly Entry[] s_sharedTable = new Entry[65536];
|
|
|
|
private int _localRandom = Environment.TickCount;
|
|
|
|
private static int s_sharedRandom = Environment.TickCount;
|
|
|
|
private readonly ObjectPool<StringTable>? _pool;
|
|
|
|
private static readonly ObjectPool<StringTable> s_staticPool = CreatePool();
|
|
|
|
internal StringTable()
|
|
: this(null)
|
|
{
|
|
}
|
|
|
|
private StringTable(ObjectPool<StringTable>? pool)
|
|
{
|
|
_pool = pool;
|
|
}
|
|
|
|
private static ObjectPool<StringTable> CreatePool()
|
|
{
|
|
return new ObjectPool<StringTable>((ObjectPool<StringTable> pool) => new StringTable(pool), Environment.ProcessorCount * 2);
|
|
}
|
|
|
|
public static StringTable GetInstance()
|
|
{
|
|
return s_staticPool.Allocate();
|
|
}
|
|
|
|
public void Free()
|
|
{
|
|
_pool?.Free(this);
|
|
}
|
|
|
|
internal string Add(char[] chars, int start, int len)
|
|
{
|
|
Span<char> span = chars.AsSpan(start, len);
|
|
int fNVHashCode = Hash.GetFNVHashCode(chars, start, len);
|
|
Entry[] localTable = _localTable;
|
|
int num = LocalIdxFromHash(fNVHashCode);
|
|
if (localTable[num].Text != null && localTable[num].HashCode == fNVHashCode)
|
|
{
|
|
string text = localTable[num].Text;
|
|
if (TextEquals(text, span))
|
|
{
|
|
return text;
|
|
}
|
|
}
|
|
string text2 = FindSharedEntry(chars, start, len, fNVHashCode);
|
|
if (text2 != null)
|
|
{
|
|
localTable[num].HashCode = fNVHashCode;
|
|
localTable[num].Text = text2;
|
|
return text2;
|
|
}
|
|
return AddItem(chars, start, len, fNVHashCode);
|
|
}
|
|
|
|
internal string Add(string chars, int start, int len)
|
|
{
|
|
int fNVHashCode = Hash.GetFNVHashCode(chars, start, len);
|
|
Entry[] localTable = _localTable;
|
|
int num = LocalIdxFromHash(fNVHashCode);
|
|
if (localTable[num].Text != null && localTable[num].HashCode == fNVHashCode)
|
|
{
|
|
string text = localTable[num].Text;
|
|
if (TextEquals(text, chars, start, len))
|
|
{
|
|
return text;
|
|
}
|
|
}
|
|
string text2 = FindSharedEntry(chars, start, len, fNVHashCode);
|
|
if (text2 != null)
|
|
{
|
|
localTable[num].HashCode = fNVHashCode;
|
|
localTable[num].Text = text2;
|
|
return text2;
|
|
}
|
|
return AddItem(chars, start, len, fNVHashCode);
|
|
}
|
|
|
|
internal string Add(char chars)
|
|
{
|
|
int fNVHashCode = Hash.GetFNVHashCode(chars);
|
|
Entry[] localTable = _localTable;
|
|
int num = LocalIdxFromHash(fNVHashCode);
|
|
string text = localTable[num].Text;
|
|
if (text != null)
|
|
{
|
|
string text2 = localTable[num].Text;
|
|
if (text.Length == 1 && text[0] == chars)
|
|
{
|
|
return text2;
|
|
}
|
|
}
|
|
string text3 = FindSharedEntry(chars, fNVHashCode);
|
|
if (text3 != null)
|
|
{
|
|
localTable[num].HashCode = fNVHashCode;
|
|
localTable[num].Text = text3;
|
|
return text3;
|
|
}
|
|
return AddItem(chars, fNVHashCode);
|
|
}
|
|
|
|
internal string Add(StringBuilder chars)
|
|
{
|
|
int fNVHashCode = Hash.GetFNVHashCode(chars);
|
|
Entry[] localTable = _localTable;
|
|
int num = LocalIdxFromHash(fNVHashCode);
|
|
if (localTable[num].Text != null && localTable[num].HashCode == fNVHashCode)
|
|
{
|
|
string text = localTable[num].Text;
|
|
if (TextEquals(text, chars))
|
|
{
|
|
return text;
|
|
}
|
|
}
|
|
string text2 = FindSharedEntry(chars, fNVHashCode);
|
|
if (text2 != null)
|
|
{
|
|
localTable[num].HashCode = fNVHashCode;
|
|
localTable[num].Text = text2;
|
|
return text2;
|
|
}
|
|
return AddItem(chars, fNVHashCode);
|
|
}
|
|
|
|
internal string Add(string chars)
|
|
{
|
|
int fNVHashCode = Hash.GetFNVHashCode(chars);
|
|
Entry[] localTable = _localTable;
|
|
int num = LocalIdxFromHash(fNVHashCode);
|
|
if (localTable[num].Text != null && localTable[num].HashCode == fNVHashCode)
|
|
{
|
|
string text = localTable[num].Text;
|
|
if (text == chars)
|
|
{
|
|
return text;
|
|
}
|
|
}
|
|
string text2 = FindSharedEntry(chars, fNVHashCode);
|
|
if (text2 != null)
|
|
{
|
|
localTable[num].HashCode = fNVHashCode;
|
|
localTable[num].Text = text2;
|
|
return text2;
|
|
}
|
|
AddCore(chars, fNVHashCode);
|
|
return chars;
|
|
}
|
|
|
|
private static string? FindSharedEntry(char[] chars, int start, int len, int hashCode)
|
|
{
|
|
Entry[] array = s_sharedTable;
|
|
int num = SharedIdxFromHash(hashCode);
|
|
string text = null;
|
|
for (int i = 1; i < 17; i++)
|
|
{
|
|
text = array[num].Text;
|
|
int hashCode2 = array[num].HashCode;
|
|
if (text == null || (hashCode2 == hashCode && TextEquals(text, chars.AsSpan(start, len))))
|
|
{
|
|
break;
|
|
}
|
|
text = null;
|
|
num = (num + i) & 0xFFFF;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
private static string? FindSharedEntry(string chars, int start, int len, int hashCode)
|
|
{
|
|
Entry[] array = s_sharedTable;
|
|
int num = SharedIdxFromHash(hashCode);
|
|
string text = null;
|
|
for (int i = 1; i < 17; i++)
|
|
{
|
|
text = array[num].Text;
|
|
int hashCode2 = array[num].HashCode;
|
|
if (text == null || (hashCode2 == hashCode && TextEquals(text, chars, start, len)))
|
|
{
|
|
break;
|
|
}
|
|
text = null;
|
|
num = (num + i) & 0xFFFF;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
private static string? FindSharedEntryASCII(int hashCode, ReadOnlySpan<byte> asciiChars)
|
|
{
|
|
Entry[] array = s_sharedTable;
|
|
int num = SharedIdxFromHash(hashCode);
|
|
string text = null;
|
|
for (int i = 1; i < 17; i++)
|
|
{
|
|
text = array[num].Text;
|
|
int hashCode2 = array[num].HashCode;
|
|
if (text == null || (hashCode2 == hashCode && TextEqualsASCII(text, asciiChars)))
|
|
{
|
|
break;
|
|
}
|
|
text = null;
|
|
num = (num + i) & 0xFFFF;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
private static string? FindSharedEntry(char chars, int hashCode)
|
|
{
|
|
Entry[] array = s_sharedTable;
|
|
int num = SharedIdxFromHash(hashCode);
|
|
string text = null;
|
|
for (int i = 1; i < 17; i++)
|
|
{
|
|
text = array[num].Text;
|
|
if (text == null || (text.Length == 1 && text[0] == chars))
|
|
{
|
|
break;
|
|
}
|
|
text = null;
|
|
num = (num + i) & 0xFFFF;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
private static string? FindSharedEntry(StringBuilder chars, int hashCode)
|
|
{
|
|
Entry[] array = s_sharedTable;
|
|
int num = SharedIdxFromHash(hashCode);
|
|
string text = null;
|
|
for (int i = 1; i < 17; i++)
|
|
{
|
|
text = array[num].Text;
|
|
int hashCode2 = array[num].HashCode;
|
|
if (text == null || (hashCode2 == hashCode && TextEquals(text, chars)))
|
|
{
|
|
break;
|
|
}
|
|
text = null;
|
|
num = (num + i) & 0xFFFF;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
private static string? FindSharedEntry(string chars, int hashCode)
|
|
{
|
|
Entry[] array = s_sharedTable;
|
|
int num = SharedIdxFromHash(hashCode);
|
|
string text = null;
|
|
for (int i = 1; i < 17; i++)
|
|
{
|
|
text = array[num].Text;
|
|
int hashCode2 = array[num].HashCode;
|
|
if (text == null || (hashCode2 == hashCode && text == chars))
|
|
{
|
|
break;
|
|
}
|
|
text = null;
|
|
num = (num + i) & 0xFFFF;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
private string AddItem(char[] chars, int start, int len, int hashCode)
|
|
{
|
|
string text = new string(chars, start, len);
|
|
AddCore(text, hashCode);
|
|
return text;
|
|
}
|
|
|
|
private string AddItem(string chars, int start, int len, int hashCode)
|
|
{
|
|
string text = chars.Substring(start, len);
|
|
AddCore(text, hashCode);
|
|
return text;
|
|
}
|
|
|
|
private string AddItem(char chars, int hashCode)
|
|
{
|
|
string text = new string(chars, 1);
|
|
AddCore(text, hashCode);
|
|
return text;
|
|
}
|
|
|
|
private string AddItem(StringBuilder chars, int hashCode)
|
|
{
|
|
string text = chars.ToString();
|
|
AddCore(text, hashCode);
|
|
return text;
|
|
}
|
|
|
|
private void AddCore(string chars, int hashCode)
|
|
{
|
|
AddSharedEntry(hashCode, chars);
|
|
Entry[] localTable = _localTable;
|
|
int num = LocalIdxFromHash(hashCode);
|
|
localTable[num].HashCode = hashCode;
|
|
localTable[num].Text = chars;
|
|
}
|
|
|
|
private void AddSharedEntry(int hashCode, string text)
|
|
{
|
|
Entry[] array = s_sharedTable;
|
|
int num = SharedIdxFromHash(hashCode);
|
|
int num2 = num;
|
|
int num3 = 1;
|
|
while (true)
|
|
{
|
|
if (num3 < 17)
|
|
{
|
|
if (array[num2].Text == null)
|
|
{
|
|
num = num2;
|
|
break;
|
|
}
|
|
num2 = (num2 + num3) & 0xFFFF;
|
|
num3++;
|
|
continue;
|
|
}
|
|
int num4 = LocalNextRandom() & 0xF;
|
|
num = (num + (num4 * num4 + num4) / 2) & 0xFFFF;
|
|
break;
|
|
}
|
|
array[num].HashCode = hashCode;
|
|
Volatile.Write(ref array[num].Text, text);
|
|
}
|
|
|
|
internal static string AddShared(StringBuilder chars)
|
|
{
|
|
int fNVHashCode = Hash.GetFNVHashCode(chars);
|
|
string text = FindSharedEntry(chars, fNVHashCode);
|
|
if (text != null)
|
|
{
|
|
return text;
|
|
}
|
|
return AddSharedSlow(fNVHashCode, chars);
|
|
}
|
|
|
|
private static string AddSharedSlow(int hashCode, StringBuilder builder)
|
|
{
|
|
string text = builder.ToString();
|
|
AddSharedSlow(hashCode, text);
|
|
return text;
|
|
}
|
|
|
|
internal static string AddSharedUtf8(ReadOnlySpan<byte> bytes)
|
|
{
|
|
bool isAscii;
|
|
int fNVHashCode = Hash.GetFNVHashCode(bytes, out isAscii);
|
|
if (isAscii)
|
|
{
|
|
string text = FindSharedEntryASCII(fNVHashCode, bytes);
|
|
if (text != null)
|
|
{
|
|
return text;
|
|
}
|
|
}
|
|
return AddSharedSlow(fNVHashCode, bytes, isAscii);
|
|
}
|
|
|
|
private unsafe static string AddSharedSlow(int hashCode, ReadOnlySpan<byte> utf8Bytes, bool isAscii)
|
|
{
|
|
string text;
|
|
fixed (byte* bytes = utf8Bytes)
|
|
{
|
|
text = Encoding.UTF8.GetString(bytes, utf8Bytes.Length);
|
|
}
|
|
if (isAscii)
|
|
{
|
|
AddSharedSlow(hashCode, text);
|
|
}
|
|
return text;
|
|
}
|
|
|
|
private static void AddSharedSlow(int hashCode, string text)
|
|
{
|
|
Entry[] array = s_sharedTable;
|
|
int num = SharedIdxFromHash(hashCode);
|
|
int num2 = num;
|
|
int num3 = 1;
|
|
while (true)
|
|
{
|
|
if (num3 < 17)
|
|
{
|
|
if (array[num2].Text == null)
|
|
{
|
|
num = num2;
|
|
break;
|
|
}
|
|
num2 = (num2 + num3) & 0xFFFF;
|
|
num3++;
|
|
continue;
|
|
}
|
|
int num4 = SharedNextRandom() & 0xF;
|
|
num = (num + (num4 * num4 + num4) / 2) & 0xFFFF;
|
|
break;
|
|
}
|
|
array[num].HashCode = hashCode;
|
|
Volatile.Write(ref array[num].Text, text);
|
|
}
|
|
|
|
private static int LocalIdxFromHash(int hash)
|
|
{
|
|
return hash & 0x7FF;
|
|
}
|
|
|
|
private static int SharedIdxFromHash(int hash)
|
|
{
|
|
return (hash ^ (hash >> 11)) & 0xFFFF;
|
|
}
|
|
|
|
private int LocalNextRandom()
|
|
{
|
|
return _localRandom++;
|
|
}
|
|
|
|
private static int SharedNextRandom()
|
|
{
|
|
return Interlocked.Increment(ref s_sharedRandom);
|
|
}
|
|
|
|
internal static bool TextEquals(string array, string text, int start, int length)
|
|
{
|
|
if (array.Length != length)
|
|
{
|
|
return false;
|
|
}
|
|
for (int i = 0; i < array.Length; i++)
|
|
{
|
|
if (array[i] != text[start + i])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
internal static bool TextEquals(string array, StringBuilder text)
|
|
{
|
|
if (array.Length != text.Length)
|
|
{
|
|
return false;
|
|
}
|
|
for (int num = array.Length - 1; num >= 0; num--)
|
|
{
|
|
if (array[num] != text[num])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
internal static bool TextEqualsASCII(string text, ReadOnlySpan<byte> ascii)
|
|
{
|
|
if (ascii.Length != text.Length)
|
|
{
|
|
return false;
|
|
}
|
|
for (int i = 0; i < ascii.Length; i++)
|
|
{
|
|
if (ascii[i] != text[i])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
internal static bool TextEquals(string array, ReadOnlySpan<char> text)
|
|
{
|
|
return text.Equals(array.AsSpan(), StringComparison.Ordinal);
|
|
}
|
|
}
|