diff --git a/Directory.Packages.props b/Directory.Packages.props
index df66545fc2..7efc103789 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -12,6 +12,8 @@
+
+
diff --git a/Garnet.sln b/Garnet.sln
index cf0c3713c4..e99560537d 100644
--- a/Garnet.sln
+++ b/Garnet.sln
@@ -1,4 +1,4 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
+Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31808.319
MinimumVisualStudioVersion = 10.0.40219.1
@@ -96,6 +96,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommandInfoUpdater", "playg
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleModule", "playground\SampleModule\SampleModule.csproj", "{A8CA619E-8F13-4EF8-943F-2D5E3FEBFB3F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Garnet.gen", "libs\gen\Garnet.gen.csproj", "{E248012E-1D19-4114-924F-EFC9E859C4CD}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GarnetJSON", "playground\GarnetJSON\GarnetJSON.csproj", "{2C8F1F5D-31E5-4D00-A46E-F3B1D9BC098F}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MigrateBench", "playground\MigrateBench\MigrateBench.csproj", "{6B66B394-E410-4B61-9A5A-1595FF6F5E08}"
EndProject
@@ -289,6 +291,14 @@ Global
{A8CA619E-8F13-4EF8-943F-2D5E3FEBFB3F}.Release|Any CPU.Build.0 = Release|Any CPU
{A8CA619E-8F13-4EF8-943F-2D5E3FEBFB3F}.Release|x64.ActiveCfg = Release|Any CPU
{A8CA619E-8F13-4EF8-943F-2D5E3FEBFB3F}.Release|x64.Build.0 = Release|Any CPU
+ {E248012E-1D19-4114-924F-EFC9E859C4CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E248012E-1D19-4114-924F-EFC9E859C4CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E248012E-1D19-4114-924F-EFC9E859C4CD}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E248012E-1D19-4114-924F-EFC9E859C4CD}.Debug|x64.Build.0 = Debug|Any CPU
+ {E248012E-1D19-4114-924F-EFC9E859C4CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E248012E-1D19-4114-924F-EFC9E859C4CD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E248012E-1D19-4114-924F-EFC9E859C4CD}.Release|x64.ActiveCfg = Release|Any CPU
+ {E248012E-1D19-4114-924F-EFC9E859C4CD}.Release|x64.Build.0 = Release|Any CPU
{2C8F1F5D-31E5-4D00-A46E-F3B1D9BC098F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C8F1F5D-31E5-4D00-A46E-F3B1D9BC098F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C8F1F5D-31E5-4D00-A46E-F3B1D9BC098F}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -342,6 +352,7 @@ Global
{9F6E4734-6341-4A9C-A7FF-636A39D8BEAD} = {346A5A53-51E4-4A75-B7E6-491D950382CE}
{9BE474A2-1547-43AC-B4F2-FB48A01FA995} = {69A71E2C-00E3-42F3-854E-BE157A24834E}
{A8CA619E-8F13-4EF8-943F-2D5E3FEBFB3F} = {69A71E2C-00E3-42F3-854E-BE157A24834E}
+ {E248012E-1D19-4114-924F-EFC9E859C4CD} = {147FCE31-EC09-4C90-8E4D-37CA87ED18C3}
{2C8F1F5D-31E5-4D00-A46E-F3B1D9BC098F} = {69A71E2C-00E3-42F3-854E-BE157A24834E}
{6B66B394-E410-4B61-9A5A-1595FF6F5E08} = {69A71E2C-00E3-42F3-854E-BE157A24834E}
{697766CD-2046-46D9-958A-0FD3B46C98D4} = {01823EA4-4446-4D66-B268-DFEE55951964}
diff --git a/libs/common/EnumUtils.cs b/libs/common/EnumUtils.cs
deleted file mode 100644
index 07adc9f380..0000000000
--- a/libs/common/EnumUtils.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-
-namespace Garnet.common
-{
- ///
- /// Utilities for enums
- ///
- public static class EnumUtils
- {
- private static readonly Dictionary> EnumNameToDescriptionCache = new();
- private static readonly Dictionary>> EnumDescriptionToNameCache = new();
-
- ///
- /// Gets a mapping between an enum's string value to its description, for each of the enum's values
- ///
- /// Enum type
- /// A dictionary mapping between the enum's string value to its description
- public static IDictionary GetEnumNameToDescription() where T : Enum
- {
- // Check if mapping is already in the cache. If not, add it to the cache.
- if (!EnumNameToDescriptionCache.ContainsKey(typeof(T)))
- AddTypeToCache();
-
- return EnumNameToDescriptionCache[typeof(T)];
- }
-
- ///
- /// If enum does not have the 'Flags' attribute, gets an array of size 1 with the description of the enum's value.
- /// If no description exists, returns the ToString() value of the input value.
- /// If enum has the 'Flags' attribute, gets an array with all the descriptions of the flags which are turned on in the input value.
- /// If no description exists, returns the ToString() value of the flag.
- ///
- /// Enum type
- /// Enum value
- /// Array of descriptions
- public static string[] GetEnumDescriptions(T value) where T : Enum
- {
- var nameToDesc = GetEnumNameToDescription();
- return value.ToString().Split(',').Select(f => nameToDesc.ContainsKey(f.Trim()) ? nameToDesc[f.Trim()] : f).ToArray();
- }
-
- ///
- /// Gets an enum's values based on the description attribute
- ///
- /// Enum type
- /// Enum description
- /// Enum values
- /// True if matched more than one value successfully
- public static bool TryParseEnumsFromDescription(string strVal, out IEnumerable vals) where T : struct, Enum
- {
- vals = new List();
-
- if (!EnumDescriptionToNameCache.ContainsKey(typeof(T)))
- AddTypeToCache();
-
- if (!EnumDescriptionToNameCache[typeof(T)].ContainsKey(strVal))
- return false;
-
- foreach (var enumName in EnumDescriptionToNameCache[typeof(T)][strVal])
- {
- if (Enum.TryParse(enumName, out T enumVal))
- {
- ((List)vals).Add(enumVal);
- }
- }
-
- return ((List)vals).Count > 0;
- }
-
- ///
- /// Gets an enum's value based on its description attribute
- /// If more than one values match the same description, returns the first one
- ///
- /// Enum type
- /// Enum description
- /// Enum value
- /// True if successful
- public static bool TryParseEnumFromDescription(string strVal, out T val) where T : struct, Enum
- {
- var isSuccessful = TryParseEnumsFromDescription(strVal, out IEnumerable vals);
- val = isSuccessful ? vals.First() : default;
- return isSuccessful;
- }
-
-
- private static void AddTypeToCache()
- {
- var valToDesc = new Dictionary();
- var descToVals = new Dictionary>();
-
- foreach (var flagFieldInfo in typeof(T).GetFields())
- {
- var descAttr = (DescriptionAttribute)flagFieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault();
- if (descAttr != null)
- {
- valToDesc.Add(flagFieldInfo.Name, descAttr.Description);
- if (!descToVals.ContainsKey(descAttr.Description))
- descToVals.Add(descAttr.Description, new List());
- descToVals[descAttr.Description].Add(flagFieldInfo.Name);
- }
- }
-
- EnumNameToDescriptionCache.Add(typeof(T), valToDesc);
- EnumDescriptionToNameCache.Add(typeof(T), descToVals);
- }
- }
-}
\ No newline at end of file
diff --git a/libs/common/GenerateEnumUtilsAttribute.cs b/libs/common/GenerateEnumUtilsAttribute.cs
new file mode 100644
index 0000000000..05042925a3
--- /dev/null
+++ b/libs/common/GenerateEnumUtilsAttribute.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System;
+
+namespace Garnet.common
+{
+ ///
+ /// Specifies that utility methods for generating enum descriptions should be generated for the target enum.
+ ///
+ [AttributeUsage(AttributeTargets.Enum)]
+ public sealed class GenerateEnumDescriptionUtilsAttribute : Attribute
+ {
+ }
+}
\ No newline at end of file
diff --git a/libs/gen/EnumsSourceGenerator.cs b/libs/gen/EnumsSourceGenerator.cs
new file mode 100644
index 0000000000..9697b7ad5d
--- /dev/null
+++ b/libs/gen/EnumsSourceGenerator.cs
@@ -0,0 +1,187 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System.CodeDom.Compiler;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Garnet;
+
+[Generator]
+public class EnumsSourceGenerator : IIncrementalGenerator
+{
+ const string GeneratedClassName = "EnumUtils";
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var enumDetails = context.SyntaxProvider
+ .ForAttributeWithMetadataName(
+ "Garnet.common.GenerateEnumDescriptionUtilsAttribute",
+ predicate: static (_, _) => true,
+ transform: static (ctx, _) => TransformEnumDetails((EnumDeclarationSyntax)ctx.TargetNode, ctx.SemanticModel)
+ );
+ var enumUtils = enumDetails.Select(static (details, _) => Execute(details));
+
+ context.RegisterSourceOutput(enumUtils, (ctx, source) => ctx.AddSource($"{GeneratedClassName}.{source.EnumName}.g.cs", source.ClassSource));
+ }
+
+ private static EnumDetails TransformEnumDetails(EnumDeclarationSyntax enumDeclaration, SemanticModel semanticModel)
+ {
+ var namespaceDeclaration = enumDeclaration.FirstAncestorOrSelf();
+ var enumName = enumDeclaration.Identifier.Text;
+
+ var values = enumDeclaration.Members
+ .Select(m => (
+ m.Identifier.Text,
+ semanticModel.GetOperation(m.EqualsValue!.Value)!.ConstantValue.Value,
+ Description: m.AttributeLists
+ .SelectMany(al => al.Attributes).Where(a => a.Name.ToString() == "Description")
+ .SingleOrDefault()?.ArgumentList?.Arguments.Single().ToString()
+ ))
+ .ToArray();
+ return new EnumDetails(namespaceDeclaration!.Name.ToString(), enumName, new EquatableArray<(string Name, object? Value, string? Description)>(values));
+ }
+
+ private static (string EnumName, string ClassSource) Execute(EnumDetails details)
+ {
+ using var classWriter = new IndentedTextWriter(new StringWriter());
+ classWriter.WriteLine("// ");
+ classWriter.WriteLine($"// This code was generated by the {nameof(EnumsSourceGenerator)} source generator.");
+ classWriter.WriteLine("// ");
+
+ classWriter.WriteLine("using System;");
+ classWriter.WriteLine("using System.ComponentModel;");
+ classWriter.WriteLine("using System.Numerics;");
+ classWriter.WriteLine();
+ classWriter.WriteLine($"namespace {details.Namespace};");
+ classWriter.WriteLine();
+ classWriter.WriteLine("/// ");
+ classWriter.WriteLine($"/// Utility methods for enums.");
+ classWriter.WriteLine("/// ");
+ classWriter.WriteLine($"public static partial class {GeneratedClassName}");
+ classWriter.WriteLine("{");
+ classWriter.Indent++;
+ GenerateTryParseEnumFromDescriptionMethod(classWriter, details);
+ classWriter.WriteLine();
+ GenerateGetEnumDescriptionsMethod(classWriter, details);
+ classWriter.Indent--;
+ classWriter.WriteLine("}");
+
+ return (details.EnumName, classWriter.InnerWriter.ToString()!);
+ }
+
+ private static void GenerateTryParseEnumFromDescriptionMethod(IndentedTextWriter classWriter, EnumDetails details)
+ {
+ classWriter.WriteLine("/// ");
+ classWriter.WriteLine("/// Tries to parse the enum value from the description.");
+ classWriter.WriteLine("/// ");
+ classWriter.WriteLine("/// Enum description.");
+ classWriter.WriteLine("/// Enum value.");
+ classWriter.WriteLine("/// True if successful.");
+ classWriter.WriteLine($"public static bool TryParse{details.EnumName}FromDescription(string description, out {details.EnumName} result)");
+ classWriter.WriteLine("{");
+ classWriter.Indent++;
+ classWriter.WriteLine("result = default;");
+ classWriter.WriteLine("switch (description)");
+ classWriter.WriteLine("{");
+ classWriter.Indent++;
+ foreach (var (name, _, description) in details.Values)
+ {
+ bool hasDescription = false;
+ if (description is not null)
+ {
+ hasDescription = true;
+ classWriter.WriteLine($"case {description}:");
+ }
+
+ if (!hasDescription) continue;
+ classWriter.Indent++;
+ classWriter.WriteLine($"result = {details.EnumName}.{name};");
+ classWriter.WriteLine("return true;");
+ classWriter.Indent--;
+ }
+ classWriter.Indent--;
+ classWriter.WriteLine("}");
+ classWriter.WriteLine();
+ classWriter.WriteLine("return false;");
+ classWriter.Indent--;
+ classWriter.WriteLine("}");
+ classWriter.WriteLine();
+ }
+
+ private static void GenerateGetEnumDescriptionsMethod(IndentedTextWriter classWriter, EnumDetails details)
+ {
+ classWriter.WriteLine("/// ");
+ classWriter.WriteLine("/// Gets the descriptions of the set flags. Assumes the enum is a flags enum.");
+ classWriter.WriteLine("/// If no description exists, returns the ToString() value of the input value.");
+ classWriter.WriteLine("/// ");
+ classWriter.WriteLine("/// Enum value.");
+ classWriter.WriteLine("/// Array of descriptions.");
+ classWriter.WriteLine($"public static string[] Get{details.EnumName}Descriptions({details.EnumName} value)");
+ classWriter.WriteLine("{");
+ classWriter.Indent++;
+ foreach (var (name, value, description) in details.Values)
+ {
+ if (!IsPow2(value))
+ {
+ var toStringValue = description ?? $"\"{name}\"";
+ classWriter.WriteLine($"if (value is {details.EnumName}.{name}) return [{toStringValue}];");
+ }
+ }
+ classWriter.WriteLine("var setFlags = BitOperations.PopCount((ulong)value);");
+ classWriter.WriteLine("if (setFlags == 1)");
+ classWriter.WriteLine("{");
+ classWriter.Indent++;
+ classWriter.WriteLine("return value switch");
+ classWriter.WriteLine("{");
+ classWriter.Indent++;
+ foreach (var (name, value, description) in details.Values)
+ {
+ if (description is null) continue;
+ if (!IsPow2(value)) continue;
+ classWriter.WriteLine($"{details.EnumName}.{name} => [{description}],");
+ }
+ classWriter.WriteLine($"_ => [value.ToString()],");
+ classWriter.Indent--;
+ classWriter.WriteLine("};");
+ classWriter.Indent--;
+ classWriter.WriteLine("}");
+ classWriter.WriteLine();
+ classWriter.WriteLine("var descriptions = new string[setFlags];");
+ classWriter.WriteLine("var index = 0;");
+ foreach (var (name, value, description) in details.Values)
+ {
+ if (description is null) continue;
+ if (!IsPow2(value)) continue;
+ classWriter.WriteLine($"if ((value & {details.EnumName}.{name}) != 0) descriptions[index++] = {description};");
+ }
+ classWriter.WriteLine();
+ classWriter.WriteLine("return descriptions;");
+ classWriter.Indent--;
+ classWriter.WriteLine("}");
+ }
+
+ static bool IsPow2(object? value)
+ {
+ if (value is int x) return x != 0 && (x & (x - 1)) == 0;
+ return false;
+ }
+
+ private record struct EnumDetails(string Namespace, string EnumName, EquatableArray<(string Name, object? Value, string? Description)> Values);
+
+ private class EnumDetailsComparer : IEqualityComparer
+ {
+ public bool Equals(EnumDetails x, EnumDetails y) => x.EnumName.Equals(y.EnumName) && x.Namespace.Equals(y.Namespace) && x.Values.SequenceEqual(y.Values);
+ public int GetHashCode(EnumDetails obj)
+ {
+ var hash = new HashCode();
+ hash.Add(obj.EnumName);
+ hash.Add(obj.Namespace);
+ foreach (var value in obj.Values)
+ {
+ hash.Add(value);
+ }
+ return hash.ToHashCode();
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/gen/EquatableArray.cs b/libs/gen/EquatableArray.cs
new file mode 100644
index 0000000000..4ee9665644
--- /dev/null
+++ b/libs/gen/EquatableArray.cs
@@ -0,0 +1,113 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+
+namespace Garnet;
+
+///
+/// An immutable, equatable array. This is equivalent to but with value equality support.
+///
+/// The type of values in the array.
+internal readonly struct EquatableArray : IEquatable>, IEnumerable
+ where T : IEquatable
+{
+ public static readonly EquatableArray Empty = new([]);
+
+ ///
+ /// The underlying array.
+ ///
+ private readonly T[]? _array;
+
+ ///
+ /// Creates a new instance.
+ ///
+ /// The input to wrap.
+ public EquatableArray(T[] array)
+ {
+ _array = array;
+ }
+
+ ///
+ public bool Equals(EquatableArray array)
+ {
+ return AsSpan().SequenceEqual(array.AsSpan());
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is EquatableArray array && Equals(this, array);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ if (_array is not T[] array)
+ {
+ return 0;
+ }
+
+ HashCode hashCode = default;
+
+ foreach (T item in array)
+ {
+ hashCode.Add(item);
+ }
+
+ return hashCode.ToHashCode();
+ }
+
+ ///
+ /// Returns a wrapping the current items.
+ ///
+ /// A wrapping the current items.
+ public ReadOnlySpan AsSpan()
+ {
+ return _array.AsSpan();
+ }
+
+ ///
+ /// Gets the underlying array if there is one
+ ///
+ public T[]? GetArray() => _array;
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)(_array ?? [])).GetEnumerator();
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)(_array ?? [])).GetEnumerator();
+ }
+
+ public int Count => _array?.Length ?? 0;
+
+ ///
+ /// Checks whether two values are the same.
+ ///
+ /// The first value.
+ /// The second value.
+ /// Whether and are equal.
+ public static bool operator ==(EquatableArray left, EquatableArray right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Checks whether two values are not the same.
+ ///
+ /// The first value.
+ /// The second value.
+ /// Whether and are not equal.
+ public static bool operator !=(EquatableArray left, EquatableArray right)
+ {
+ return !left.Equals(right);
+ }
+}
\ No newline at end of file
diff --git a/libs/gen/Garnet.gen.csproj b/libs/gen/Garnet.gen.csproj
new file mode 100644
index 0000000000..795c1ed291
--- /dev/null
+++ b/libs/gen/Garnet.gen.csproj
@@ -0,0 +1,22 @@
+
+
+
+ netstandard2.0
+ enable
+ enable
+ Latest
+ true
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/libs/gen/HashCode.cs b/libs/gen/HashCode.cs
new file mode 100644
index 0000000000..a015da414b
--- /dev/null
+++ b/libs/gen/HashCode.cs
@@ -0,0 +1,384 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Garnet;
+
+///
+/// Polyfill for .NET 6 HashCode
+///
+internal struct HashCode
+{
+ private static readonly uint s_seed = GenerateGlobalSeed();
+
+ private const uint Prime1 = 2654435761U;
+ private const uint Prime2 = 2246822519U;
+ private const uint Prime3 = 3266489917U;
+ private const uint Prime4 = 668265263U;
+ private const uint Prime5 = 374761393U;
+
+ private uint _v1, _v2, _v3, _v4;
+ private uint _queue1, _queue2, _queue3;
+ private uint _length;
+
+ private static uint GenerateGlobalSeed()
+ {
+ var buffer = new byte[sizeof(uint)];
+ new Random().NextBytes(buffer);
+ return BitConverter.ToUInt32(buffer, 0);
+ }
+
+ public static int Combine(T1 value1)
+ {
+ // Provide a way of diffusing bits from something with a limited
+ // input hash space. For example, many enums only have a few
+ // possible hashes, only using the bottom few bits of the code. Some
+ // collections are built on the assumption that hashes are spread
+ // over a larger space, so diffusing the bits may help the
+ // collection work more efficiently.
+
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+
+ uint hash = MixEmptyState();
+ hash += 4;
+
+ hash = QueueRound(hash, hc1);
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine(T1 value1, T2 value2)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+
+ uint hash = MixEmptyState();
+ hash += 8;
+
+ hash = QueueRound(hash, hc1);
+ hash = QueueRound(hash, hc2);
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine(T1 value1, T2 value2, T3 value3)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
+
+ uint hash = MixEmptyState();
+ hash += 12;
+
+ hash = QueueRound(hash, hc1);
+ hash = QueueRound(hash, hc2);
+ hash = QueueRound(hash, hc3);
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ uint hash = MixState(v1, v2, v3, v4);
+ hash += 16;
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
+ uint hc5 = (uint)(value5?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ uint hash = MixState(v1, v2, v3, v4);
+ hash += 20;
+
+ hash = QueueRound(hash, hc5);
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
+ uint hc5 = (uint)(value5?.GetHashCode() ?? 0);
+ uint hc6 = (uint)(value6?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ uint hash = MixState(v1, v2, v3, v4);
+ hash += 24;
+
+ hash = QueueRound(hash, hc5);
+ hash = QueueRound(hash, hc6);
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
+ uint hc5 = (uint)(value5?.GetHashCode() ?? 0);
+ uint hc6 = (uint)(value6?.GetHashCode() ?? 0);
+ uint hc7 = (uint)(value7?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ uint hash = MixState(v1, v2, v3, v4);
+ hash += 28;
+
+ hash = QueueRound(hash, hc5);
+ hash = QueueRound(hash, hc6);
+ hash = QueueRound(hash, hc7);
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
+ uint hc5 = (uint)(value5?.GetHashCode() ?? 0);
+ uint hc6 = (uint)(value6?.GetHashCode() ?? 0);
+ uint hc7 = (uint)(value7?.GetHashCode() ?? 0);
+ uint hc8 = (uint)(value8?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ v1 = Round(v1, hc5);
+ v2 = Round(v2, hc6);
+ v3 = Round(v3, hc7);
+ v4 = Round(v4, hc8);
+
+ uint hash = MixState(v1, v2, v3, v4);
+ hash += 32;
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4)
+ {
+ v1 = s_seed + Prime1 + Prime2;
+ v2 = s_seed + Prime2;
+ v3 = s_seed;
+ v4 = s_seed - Prime1;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint Round(uint hash, uint input)
+ {
+ return RotateLeft(hash + input * Prime2, 13) * Prime1;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint QueueRound(uint hash, uint queuedValue)
+ {
+ return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint MixState(uint v1, uint v2, uint v3, uint v4)
+ {
+ return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18);
+ }
+
+ private static uint MixEmptyState()
+ {
+ return s_seed + Prime5;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint MixFinal(uint hash)
+ {
+ hash ^= hash >> 15;
+ hash *= Prime2;
+ hash ^= hash >> 13;
+ hash *= Prime3;
+ hash ^= hash >> 16;
+ return hash;
+ }
+
+ public void Add(T value)
+ {
+ Add(value?.GetHashCode() ?? 0);
+ }
+
+ public void Add(T value, IEqualityComparer? comparer)
+ {
+ Add(value is null ? 0 : (comparer?.GetHashCode(value) ?? value.GetHashCode()));
+ }
+
+ private void Add(int value)
+ {
+ // The original xxHash works as follows:
+ // 0. Initialize immediately. We can't do this in a struct (no
+ // default ctor).
+ // 1. Accumulate blocks of length 16 (4 uints) into 4 accumulators.
+ // 2. Accumulate remaining blocks of length 4 (1 uint) into the
+ // hash.
+ // 3. Accumulate remaining blocks of length 1 into the hash.
+
+ // There is no need for #3 as this type only accepts ints. _queue1,
+ // _queue2 and _queue3 are basically a buffer so that when
+ // ToHashCode is called we can execute #2 correctly.
+
+ // We need to initialize the xxHash32 state (_v1 to _v4) lazily (see
+ // #0) nd the last place that can be done if you look at the
+ // original code is just before the first block of 16 bytes is mixed
+ // in. The xxHash32 state is never used for streams containing fewer
+ // than 16 bytes.
+
+ // To see what's really going on here, have a look at the Combine
+ // methods.
+
+ uint val = (uint)value;
+
+ // Storing the value of _length locally shaves of quite a few bytes
+ // in the resulting machine code.
+ uint previousLength = _length++;
+ uint position = previousLength % 4;
+
+ // Switch can't be inlined.
+
+ if (position == 0)
+ _queue1 = val;
+ else if (position == 1)
+ _queue2 = val;
+ else if (position == 2)
+ _queue3 = val;
+ else // position == 3
+ {
+ if (previousLength == 3)
+ Initialize(out _v1, out _v2, out _v3, out _v4);
+
+ _v1 = Round(_v1, _queue1);
+ _v2 = Round(_v2, _queue2);
+ _v3 = Round(_v3, _queue3);
+ _v4 = Round(_v4, val);
+ }
+ }
+
+ public int ToHashCode()
+ {
+ // Storing the value of _length locally shaves of quite a few bytes
+ // in the resulting machine code.
+ uint length = _length;
+
+ // position refers to the *next* queue position in this method, so
+ // position == 1 means that _queue1 is populated; _queue2 would have
+ // been populated on the next call to Add.
+ uint position = length % 4;
+
+ // If the length is less than 4, _v1 to _v4 don't contain anything
+ // yet. xxHash32 treats this differently.
+
+ uint hash = length < 4 ? MixEmptyState() : MixState(_v1, _v2, _v3, _v4);
+
+ // _length is incremented once per Add(Int32) and is therefore 4
+ // times too small (xxHash length is in bytes, not ints).
+
+ hash += length * 4;
+
+ // Mix what remains in the queue
+
+ // Switch can't be inlined right now, so use as few branches as
+ // possible by manually excluding impossible scenarios (position > 1
+ // is always false if position is not > 0).
+ if (position > 0)
+ {
+ hash = QueueRound(hash, _queue1);
+ if (position > 1)
+ {
+ hash = QueueRound(hash, _queue2);
+ if (position > 2)
+ hash = QueueRound(hash, _queue3);
+ }
+ }
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+#pragma warning disable 0809
+ // Obsolete member 'memberA' overrides non-obsolete member 'memberB'.
+ // Disallowing GetHashCode and Equals is by design
+
+ // * We decided to not override GetHashCode() to produce the hash code
+ // as this would be weird, both naming-wise as well as from a
+ // behavioral standpoint (GetHashCode() should return the object's
+ // hash code, not the one being computed).
+
+ // * Even though ToHashCode() can be called safely multiple times on
+ // this implementation, it is not part of the contract. If the
+ // implementation has to change in the future we don't want to worry
+ // about people who might have incorrectly used this type.
+
+ [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode() => throw new NotSupportedException("Hash code not supported");
+
+ [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object? obj) => throw new NotSupportedException("Equality not supported");
+#pragma warning restore 0809
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint RotateLeft(uint value, int offset)
+ => (value << offset) | (value >> (32 - offset));
+}
\ No newline at end of file
diff --git a/libs/server/Garnet.server.csproj b/libs/server/Garnet.server.csproj
index 596927fcd4..dd0620c8bf 100644
--- a/libs/server/Garnet.server.csproj
+++ b/libs/server/Garnet.server.csproj
@@ -5,6 +5,7 @@
../../Garnet.snk
false
true
+ true
@@ -27,4 +28,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/server/Resp/RespCommandInfoFlags.cs b/libs/server/Resp/RespCommandInfoFlags.cs
index d2cfcd9c30..f9513fbad1 100644
--- a/libs/server/Resp/RespCommandInfoFlags.cs
+++ b/libs/server/Resp/RespCommandInfoFlags.cs
@@ -4,6 +4,7 @@
using System;
using System.ComponentModel;
using System.Net.NetworkInformation;
+using Garnet.common;
namespace Garnet.server
{
@@ -11,6 +12,7 @@ namespace Garnet.server
/// RESP command flags
///
[Flags]
+ [GenerateEnumDescriptionUtils]
public enum RespCommandFlags
{
None = 0,
@@ -62,6 +64,7 @@ public enum RespCommandFlags
/// RESP ACL categories
///
[Flags]
+ [GenerateEnumDescriptionUtils]
public enum RespAclCategories
{
None = 0,
diff --git a/libs/server/Resp/RespCommandInfoParser.cs b/libs/server/Resp/RespCommandInfoParser.cs
index b79b259819..2fa34e46f8 100644
--- a/libs/server/Resp/RespCommandInfoParser.cs
+++ b/libs/server/Resp/RespCommandInfoParser.cs
@@ -46,7 +46,7 @@ public static unsafe bool TryReadFromResp(ref byte* ptr, byte* end, IReadOnlyDic
for (var flagIdx = 0; flagIdx < flagCount; flagIdx++)
{
if (!RespReadUtils.ReadSimpleString(out var strFlag, ref ptr, end)
- || !EnumUtils.TryParseEnumFromDescription(strFlag, out var flag))
+ || !EnumUtils.TryParseRespCommandFlagsFromDescription(strFlag, out var flag))
return false;
flags |= flag;
}
@@ -69,7 +69,7 @@ public static unsafe bool TryReadFromResp(ref byte* ptr, byte* end, IReadOnlyDic
for (var aclCatIdx = 0; aclCatIdx < aclCatCount; aclCatIdx++)
{
if (!RespReadUtils.ReadSimpleString(out var strAclCat, ref ptr, end)
- || !EnumUtils.TryParseEnumFromDescription(strAclCat.TrimStart('@'), out var aclCat))
+ || !EnumUtils.TryParseRespAclCategoriesFromDescription(strAclCat.TrimStart('@'), out var aclCat))
return false;
aclCategories |= aclCat;
}
@@ -153,7 +153,7 @@ internal static unsafe bool TryReadFromResp(ref byte* ptr, byte* end, out RespCo
for (var flagIdx = 0; flagIdx < flagsCount; flagIdx++)
{
if (!RespReadUtils.ReadSimpleString(out var strFlag, ref ptr, end)
- || !EnumUtils.TryParseEnumFromDescription(strFlag, out var flag))
+ || !EnumUtils.TryParseKeySpecificationFlagsFromDescription(strFlag, out var flag))
return false;
flags |= flag;
}
diff --git a/libs/server/Resp/RespCommandKeySpecification.cs b/libs/server/Resp/RespCommandKeySpecification.cs
index f11292b8a9..93e14a35c2 100644
--- a/libs/server/Resp/RespCommandKeySpecification.cs
+++ b/libs/server/Resp/RespCommandKeySpecification.cs
@@ -40,7 +40,7 @@ public KeySpecificationFlags Flags
init
{
this.flags = value;
- this.respFormatFlags = EnumUtils.GetEnumDescriptions(this.flags);
+ this.respFormatFlags = EnumUtils.GetKeySpecificationFlagsDescriptions(this.flags);
}
}
@@ -100,6 +100,7 @@ public string ToRespFormat()
/// RESP key specification flags
///
[Flags]
+ [GenerateEnumDescriptionUtils]
public enum KeySpecificationFlags : ushort
{
None = 0,
diff --git a/libs/server/Resp/RespCommandsInfo.cs b/libs/server/Resp/RespCommandsInfo.cs
index 81f9455841..4ed8e34304 100644
--- a/libs/server/Resp/RespCommandsInfo.cs
+++ b/libs/server/Resp/RespCommandsInfo.cs
@@ -52,7 +52,7 @@ public RespCommandFlags Flags
init
{
this.flags = value;
- this.respFormatFlags = EnumUtils.GetEnumDescriptions(this.flags);
+ this.respFormatFlags = EnumUtils.GetRespCommandFlagsDescriptions(this.flags);
}
}
@@ -80,7 +80,7 @@ public RespAclCategories AclCategories
init
{
this.aclCategories = value;
- this.respFormatAclCategories = EnumUtils.GetEnumDescriptions(this.aclCategories);
+ this.respFormatAclCategories = EnumUtils.GetRespAclCategoriesDescriptions(this.aclCategories);
}
}