diff --git a/src/System.Runtime/ref/System.Runtime.cs b/src/System.Runtime/ref/System.Runtime.cs index 82ffee5373de..58ff8f7f32b7 100644 --- a/src/System.Runtime/ref/System.Runtime.cs +++ b/src/System.Runtime/ref/System.Runtime.cs @@ -3675,6 +3675,43 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public void SetTarget(T target) { } public bool TryGetTarget(out T target) { throw null; } } + + public partial struct HashCode + { + public static int Combine(T1 value1) { throw null; } + + public static int Combine(T1 value1, T2 value2) { throw null; } + + public static int Combine(T1 value1, T2 value2, T3 value3) { throw null; } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) { throw null; } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) { throw null; } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) { throw null; } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) { throw null; } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) { throw null; } + + public void Add(T value) { } + + public void Add(T value, System.Collections.Generic.IEqualityComparer comparer) { } + + public int ToHashCode() { throw null; } + +#pragma warning disable 0809 + + [System.ObsoleteAttribute("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)] + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public override int GetHashCode() { throw null; } + + [System.ObsoleteAttribute("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public override bool Equals(object obj) { throw null; } + +#pragma warning restore 0809 + } } namespace System.Runtime.ConstrainedExecution diff --git a/src/System.Runtime/tests/Performance/Perf.HashCode.cs b/src/System.Runtime/tests/Performance/Perf.HashCode.cs new file mode 100644 index 000000000000..d3def90ff7f3 --- /dev/null +++ b/src/System.Runtime/tests/Performance/Perf.HashCode.cs @@ -0,0 +1,209 @@ +// 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.Runtime.CompilerServices; +using Microsoft.Xunit.Performance; + +namespace System.Tests +{ + public class Perf_HashCode + { + private static volatile int _valueStorage; + + // Prevents the jitter from eliminating code that + // we want to test. + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void DontDiscard(int value) + { + _valueStorage = value; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int DontFold(int value) + { + return value + _valueStorage; + } + + [Benchmark] + public void Add() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 100; i++) + { + var hc = new HashCode(); + for (int j = 0; j < 100; j++) + { + hc.Add(i); hc.Add(i); hc.Add(i); + hc.Add(i); hc.Add(i); hc.Add(i); + hc.Add(i); hc.Add(i); hc.Add(i); + } + DontDiscard(hc.ToHashCode()); + } + } + } + } + + + [Benchmark] + public void Combine_1() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i))); + } + } + } + } + + + [Benchmark] + public void Combine_2() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i))); + } + } + } + } + + [Benchmark] + public void Combine_3() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i), + DontFold(i))); + } + } + } + } + + [Benchmark] + public void Combine_4() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i))); + } + } + } + } + + [Benchmark] + public void Combine_5() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i))); + } + } + } + } + + [Benchmark] + public void Combine_6() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i))); + } + } + } + } + + [Benchmark] + public void Combine_7() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i))); + } + } + } + } + + [Benchmark] + public void Combine_8() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i))); + } + } + } + } + } +} diff --git a/src/System.Runtime/tests/Performance/System.Runtime.Performance.Tests.csproj b/src/System.Runtime/tests/Performance/System.Runtime.Performance.Tests.csproj index e7be566250ac..d65f1a0c6eb1 100644 --- a/src/System.Runtime/tests/Performance/System.Runtime.Performance.Tests.csproj +++ b/src/System.Runtime/tests/Performance/System.Runtime.Performance.Tests.csproj @@ -15,6 +15,7 @@ + diff --git a/src/System.Runtime/tests/System.Runtime.Tests.csproj b/src/System.Runtime/tests/System.Runtime.Tests.csproj index 527199e58d0c..2b444c717cce 100644 --- a/src/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/System.Runtime/tests/System.Runtime.Tests.csproj @@ -174,6 +174,7 @@ + @@ -264,4 +265,4 @@ - \ No newline at end of file + diff --git a/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs new file mode 100644 index 000000000000..80c93f7de06e --- /dev/null +++ b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs @@ -0,0 +1,295 @@ +// 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; +using System.Collections.Generic; +using Xunit; + +public static class HashCodeTests +{ + [Fact] + public static void HashCode_Add() + { + // The version of xUnit used by corefx does not support params theories. + void Theory(uint expected, params uint[] vector) + { + var hc = new HashCode(); + for (int i = 0; i < vector.Length; i++) + hc.Add(vector[i]); + +#if SYSTEM_HASHCODE_TESTVECTORS + // HashCode is not deterministic across AppDomains by design. This means + // that these tests can not be executed against the version that exists + // within CoreCLR. Copy HashCode and set m_seed to 0 in order to execute + // these tests. + + Assert.Equal(expected, (uint)hc.ToHashCode()); +#else + // Validate that the HashCode.m_seed is randomized. This has a 1 in 4 + // billion chance of resulting in a false negative, as HashCode.m_seed + // can be 0. + + Assert.NotEqual(expected, (uint)hc.ToHashCode()); +#endif + } + + // These test vectors were created using https://asecuritysite.com/encryption/xxHash + // 1. Find the hash for "". + // 2. Find the hash for "abcd". ASCII "abcd" and bit convert to uint. + // 3. Find the hash for "abcd1234". ASCII [ "abcd", "1234"] and bit convert to 2 uints. + // n. Continue until "abcd0123efgh4567ijkl8901mnop2345qrst6789uvwx0123yzab". + + Theory(0x02cc5d05U); + Theory(0xa3643705U, 0x64636261U ); + Theory(0x4603e94cU, 0x64636261U, 0x33323130U ); + Theory(0xd8a1e80fU, 0x64636261U, 0x33323130U, 0x68676665U ); + Theory(0x4b62a7cfU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U ); + Theory(0xc33a7641U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U ); + Theory(0x1a794705U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U ); + Theory(0x4d79177dU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU ); + Theory(0x59d79205U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U ); + Theory(0x49585aaeU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U ); + Theory(0x2f005ff1U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U ); + Theory(0x0ce339bdU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U, 0x78777675U ); + Theory(0xb31bd2ffU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U, 0x78777675U, 0x33323130U ); + Theory(0xa821efa3U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U, 0x78777675U, 0x33323130U, 0x62617a79U ); + } + + [Fact] + public static void HashCode_Add_HashCode() + { + var hc1 = new HashCode(); + hc1.Add("Hello"); + + var hc2 = new HashCode(); + hc2.Add("Hello".GetHashCode()); + + Assert.Equal(hc1.ToHashCode(), hc2.ToHashCode()); + } + + [Fact] + public static void HashCode_Add_Generic() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(new ConstHashCodeType()); + + var expected = new HashCode(); + expected.Add(1); + expected.Add(ConstComparer.ConstantValue); + + Assert.Equal(expected.ToHashCode(), hc.ToHashCode()); + } + + [Fact] + public static void HashCode_Add_Null() + { + var hc = new HashCode(); + hc.Add(null); + + var expected = new HashCode(); + expected.Add(EqualityComparer.Default.GetHashCode(null)); + + Assert.Equal(expected.ToHashCode(), hc.ToHashCode()); + } + + [Fact] + public static void HashCode_Add_GenericEqualityComparer() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add("Hello", new ConstComparer()); + + var expected = new HashCode(); + expected.Add(1); + expected.Add(ConstComparer.ConstantValue); + + Assert.Equal(expected.ToHashCode(), hc.ToHashCode()); + } + + [Fact] + public static void HashCode_Add_NullEqualityComparer() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add("Hello", null); + + var expected = new HashCode(); + expected.Add(1); + expected.Add("Hello"); + + Assert.Equal(expected.ToHashCode(), hc.ToHashCode()); + } + + [Fact] + public static void HashCode_Combine() + { + var hcs = new int[] + { + HashCode.Combine(1), + HashCode.Combine(1, 2), + HashCode.Combine(1, 2, 3), + HashCode.Combine(1, 2, 3, 4), + HashCode.Combine(1, 2, 3, 4, 5), + HashCode.Combine(1, 2, 3, 4, 5, 6), + HashCode.Combine(1, 2, 3, 4, 5, 6, 7), + HashCode.Combine(1, 2, 3, 4, 5, 6, 7, 8), + + HashCode.Combine(2), + HashCode.Combine(2, 3), + HashCode.Combine(2, 3, 4), + HashCode.Combine(2, 3, 4, 5), + HashCode.Combine(2, 3, 4, 5, 6), + HashCode.Combine(2, 3, 4, 5, 6, 7), + HashCode.Combine(2, 3, 4, 5, 6, 7, 8), + HashCode.Combine(2, 3, 4, 5, 6, 7, 8, 9), + }; + + for (int i = 0; i < hcs.Length; i++) + for (int j = 0; j < hcs.Length; j++) + { + if (i == j) continue; + Assert.NotEqual(hcs[i], hcs[j]); + } + } + + [Fact] + public static void HashCode_Combine_Add_1() + { + var hc = new HashCode(); + hc.Add(1); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1)); + } + + [Fact] + public static void HashCode_Combine_Add_2() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2)); + } + + [Fact] + public static void HashCode_Combine_Add_3() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + hc.Add(3); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2, 3)); + } + + [Fact] + public static void HashCode_Combine_Add_4() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2, 3, 4)); + } + + [Fact] + public static void HashCode_Combine_Add_5() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + hc.Add(5); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2, 3, 4, 5)); + } + + [Fact] + public static void HashCode_Combine_Add_6() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + hc.Add(5); + hc.Add(6); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2, 3, 4, 5, 6)); + } + + [Fact] + public static void HashCode_Combine_Add_7() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + hc.Add(5); + hc.Add(6); + hc.Add(7); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2, 3, 4, 5, 6, 7)); + } + + [Fact] + public static void HashCode_Combine_Add_8() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + hc.Add(5); + hc.Add(6); + hc.Add(7); + hc.Add(8); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2, 3, 4, 5, 6, 7, 8)); + } + + [Fact] + public static void HashCode_GetHashCode() + { + var hc = new HashCode(); + + Assert.Throws(() => hc.GetHashCode()); + } + + [Fact] + public static void HashCode_Equals() + { + var hc = new HashCode(); + + Assert.Throws(() => hc.Equals(hc)); + } + + [Fact] + public static void HashCode_GetHashCode_Boxed() + { + var hc = new HashCode(); + var obj = (object)hc; + + Assert.Throws(() => obj.GetHashCode()); + } + + [Fact] + public static void HashCode_Equals_Boxed() + { + var hc = new HashCode(); + var obj = (object)hc; + + Assert.Throws(() => obj.Equals(obj)); + } + + public class ConstComparer : System.Collections.Generic.IEqualityComparer + { + public const int ConstantValue = 1234; + + public bool Equals(string x, string y) => false; + public int GetHashCode(string obj) => ConstantValue; + } + + public class ConstHashCodeType + { + public override int GetHashCode() => ConstComparer.ConstantValue; + } +}