From 65d29fbbfd0189eb5554fdd147eab902f1066023 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 03:40:52 +0300 Subject: [PATCH 01/19] Unroll Equals too --- src/coreclr/jit/inlinepolicy.cpp | 18 +- .../CompilerServices/RuntimeHelpers.cs | 3 + .../src/System/String.Comparison.cs | 194 +++++++++++++----- 3 files changed, 159 insertions(+), 56 deletions(-) diff --git a/src/coreclr/jit/inlinepolicy.cpp b/src/coreclr/jit/inlinepolicy.cpp index 0ea9135b82c249..02af30d3b0512b 100644 --- a/src/coreclr/jit/inlinepolicy.cpp +++ b/src/coreclr/jit/inlinepolicy.cpp @@ -735,15 +735,6 @@ double DefaultPolicy::DetermineMultiplier() JITDUMP("\nInline candidate has arg that feeds range check. Multiplier increased to %g.", multiplier); } - if (m_ConstArgFeedsIsKnownConst || (m_ArgFeedsIsKnownConst && m_IsPrejitRoot)) - { - // if we use RuntimeHelpers.IsKnownConstant we most likely expect our function to be always inlined - // at least in the case of constant arguments. In IsPrejitRoot we don't have callsite info so let's - // assume we have a constant here in order to avoid "baked" noinline - multiplier += 20; - JITDUMP("\nConstant argument feeds RuntimeHelpers.IsKnownConstant. Multiplier increased to %g.", multiplier); - } - if (m_ConstantArgFeedsConstantTest > 0) { multiplier += 3.0; @@ -1629,6 +1620,15 @@ double ExtendedDefaultPolicy::DetermineMultiplier() } } + if (m_ConstArgFeedsIsKnownConst || (m_ArgFeedsIsKnownConst && m_IsPrejitRoot)) + { + // if we use RuntimeHelpers.IsKnownConstant we most likely expect our function to be always inlined + // at least in the case of constant arguments. In IsPrejitRoot we don't have callsite info so let's + // assume we have a constant here in order to avoid "baked" noinline + multiplier += 20; + JITDUMP("\nConstant argument feeds RuntimeHelpers.IsKnownConstant. Multiplier increased to %g.", multiplier); + } + if (m_ArgFeedsConstantTest > 0) { multiplier += m_IsPrejitRoot ? 3.0 : 1.0; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 498055ad6a1c51..9c57a49bf2b0a5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -127,5 +127,8 @@ internal static bool IsPrimitiveType(this CorElementType et) [Intrinsic] internal static bool IsKnownConstant(char t) => false; + + [Intrinsic] + internal static bool IsKnownConstant(int t) => false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 6a059351f376ac..74d4fe81e221b5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text.Unicode; +using System.Buffers.Binary; using Internal.Runtime.CompilerServices; @@ -681,31 +682,85 @@ public bool Equals([NotNullWhen(true)] string? value, StringComparison compariso } // Determines whether two Strings match. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Equals(string? a, string? b) { - // Transform 'str == ""' to 'str != null && str.Length == 0' if either a or b are jit-time - // constants. Otherwise, these two blocks are eliminated - if (RuntimeHelpers.IsKnownConstant(a) && a != null && a.Length == 0) +#if TARGET_64BIT && !MONO && !BIGENDIAN + if (RuntimeHelpers.IsKnownConstant(b) && b != null && b.Length <= 4) { - return b != null && b.Length == 0; + return EqualsUnrolled(a, b); } - if (RuntimeHelpers.IsKnownConstant(b) && b != null && b.Length == 0) + if (RuntimeHelpers.IsKnownConstant(a) && a != null && a.Length <= 4) { - return a != null && a.Length == 0; + return EqualsUnrolled(b, a); } - if (object.ReferenceEquals(a, b)) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool EqualsUnrolled(string? a, string b) { - return true; + // if both are constants - EqualsInternal will handle it just fine + if (RuntimeHelpers.IsKnownConstant(a)) + { + return EqualsInternal(a, b); + } + + if (b.Length == 0) + { + return a != null && a.Length == 0; + } + + if (b.Length == 1) + { + return a != null && + // Length: [ 0 ][ 1 ], ch1: [ X ][ \0 ] - we can compare Length and firstChar using a single + // cmp operation, it's safe because there is also '\0' char we can rely on + Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == + (((ulong)b[0] << 32) | 1UL); + } + + if (b.Length == 2) + { + // Same here, compare Length and two chars in a single cmp + return a != null && + Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == + (((ulong)b[1] << 48) | ((ulong)b[0] << 32) | 2UL); + } + + if (b.Length == 3) + { + // it's safe to load 64bit here because of '\0' but we need to mask the last char out + return a != null && a.Length == 3 && + Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == + (((ulong)b[2] << 32) | ((ulong)b[1] << 16) | (ulong)b[0]); + } + + if (b.Length == 4) + { + return a != null && a.Length == 4 && + Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == + (((ulong)b[3] << 48) | ((ulong)b[2] << 32) | ((ulong)b[1] << 16) | b[0]); + } + + return EqualsInternal(a, b); } +#endif + return EqualsInternal(a, b); - if (a is null || b is null || a.Length != b.Length) + static bool EqualsInternal(string? a, string? b) { - return false; - } + if (object.ReferenceEquals(a, b)) + { + return true; + } - return EqualsHelper(a, b); + if (a is null || b is null || a.Length != b.Length) + { + return false; + } + + return EqualsHelper(a, b); + } } public static bool Equals(string? a, string? b, StringComparison comparisonType) @@ -953,9 +1008,11 @@ public bool StartsWith(string value) { throw new ArgumentNullException(nameof(value)); } - return StartsWith(value, StringComparison.CurrentCulture); + return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, GetCaseCompareOfComparisonCulture(StringComparison.CurrentCulture)); } + // A hint for the inliner to always prescan this method + // and take the constant input path public bool StartsWith(string value, StringComparison comparisonType) { if (value is null) @@ -963,49 +1020,92 @@ public bool StartsWith(string value, StringComparison comparisonType) throw new ArgumentNullException(nameof(value)); } - if ((object)this == (object)value) +#if TARGET_64BIT && !MONO && !BIGENDIAN + if (RuntimeHelpers.IsKnownConstant(value) && RuntimeHelpers.IsKnownConstant((int)comparisonType) && + comparisonType == StringComparison.Ordinal && value.Length <= 4) { - CheckStringComparison(comparisonType); - return true; + return StartsWithUnrolledOrdinal(value); } - if (value.Length == 0) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + bool StartsWithUnrolledOrdinal(string value) { - CheckStringComparison(comparisonType); - return true; + if (value.Length == 0) + { + return true; + } + if (value.Length == 1) + { + return _stringLength >= 1 && _firstChar == value[0]; + } + if (value.Length == 2) + { + return _stringLength >= 2 && Unsafe.ReadUnaligned(ref Unsafe.As(ref _firstChar)) == + (((uint)value[1] << 16) | value[0]); + } + if (value.Length == 3) + { + // it's safe to load 64bit here because of '\0' but we need to mask the last char out + return _stringLength >= 3 && Unsafe.ReadUnaligned(ref Unsafe.As(ref _firstChar)) << 16 == + (((ulong)value[2] << 48) | ((ulong)value[1] << 32) | (ulong)value[0] << 16); + } + if (value.Length == 4) + { + return _stringLength >= 4 && Unsafe.ReadUnaligned(ref Unsafe.As(ref _firstChar)) == + (((ulong)value[3] << 48) | ((ulong)value[2] << 32) | ((ulong)value[1] << 16) | value[0]); + } + return StartsWithInternal(value, StringComparison.Ordinal); } +#endif - switch (comparisonType) - { - case StringComparison.CurrentCulture: - case StringComparison.CurrentCultureIgnoreCase: - return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, GetCaseCompareOfComparisonCulture(comparisonType)); - - case StringComparison.InvariantCulture: - case StringComparison.InvariantCultureIgnoreCase: - return CompareInfo.Invariant.IsPrefix(this, value, GetCaseCompareOfComparisonCulture(comparisonType)); + return StartsWithInternal(value, comparisonType); - case StringComparison.Ordinal: - if (this.Length < value.Length || _firstChar != value._firstChar) - { - return false; - } - return (value.Length == 1) ? - true : // First char is the same and thats all there is to compare - SpanHelpers.SequenceEqual( - ref Unsafe.As(ref this.GetRawStringData()), - ref Unsafe.As(ref value.GetRawStringData()), - ((nuint)value.Length) * 2); + bool StartsWithInternal(string value, StringComparison comparisonType) + { + if ((object)this == (object)value) + { + CheckStringComparison(comparisonType); + return true; + } - case StringComparison.OrdinalIgnoreCase: - if (this.Length < value.Length) - { - return false; - } - return Ordinal.EqualsIgnoreCase(ref this.GetRawStringData(), ref value.GetRawStringData(), value.Length); + if (value.Length == 0) + { + CheckStringComparison(comparisonType); + return true; + } - default: - throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); + switch (comparisonType) + { + case StringComparison.CurrentCulture: + case StringComparison.CurrentCultureIgnoreCase: + return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, GetCaseCompareOfComparisonCulture(comparisonType)); + + case StringComparison.InvariantCulture: + case StringComparison.InvariantCultureIgnoreCase: + return CompareInfo.Invariant.IsPrefix(this, value, GetCaseCompareOfComparisonCulture(comparisonType)); + + case StringComparison.Ordinal: + if (this.Length < value.Length || _firstChar != value._firstChar) + { + return false; + } + return (value.Length == 1) ? + true : // First char is the same and thats all there is to compare + SpanHelpers.SequenceEqual( + ref Unsafe.As(ref this.GetRawStringData()), + ref Unsafe.As(ref value.GetRawStringData()), + ((nuint)value.Length) * 2); + + case StringComparison.OrdinalIgnoreCase: + if (this.Length < value.Length) + { + return false; + } + return Ordinal.EqualsIgnoreCase(ref this.GetRawStringData(), ref value.GetRawStringData(), value.Length); + + default: + throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); + } } } From 11941a47f110e95bc61e45b92dc176eb7c598873 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 03:50:59 +0300 Subject: [PATCH 02/19] remove irrelevant comment --- .../System.Private.CoreLib/src/System/String.Comparison.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 74d4fe81e221b5..ae00dab5fe987f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -729,7 +729,7 @@ static bool EqualsUnrolled(string? a, string b) if (b.Length == 3) { - // it's safe to load 64bit here because of '\0' but we need to mask the last char out + // it's safe to load 64bit here because of '\0' return a != null && a.Length == 3 && Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == (((ulong)b[2] << 32) | ((ulong)b[1] << 16) | (ulong)b[0]); From aef78efaabaee4f31d93d311a6d783f0e9af1e2b Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 04:36:45 +0300 Subject: [PATCH 03/19] formatting --- .../src/System/String.Comparison.cs | 80 +++++++++++++++---- 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index ae00dab5fe987f..6b787a5ff88a9a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -10,6 +10,7 @@ using System.Runtime.InteropServices; using System.Text.Unicode; using System.Buffers.Binary; +using System.Runtime.Intrinsics; using Internal.Runtime.CompilerServices; @@ -699,6 +700,11 @@ public static bool Equals(string? a, string? b) [MethodImpl(MethodImplOptions.AggressiveInlining)] static bool EqualsUnrolled(string? a, string b) { + if (a == null) + { + return false; // b is not null + } + // if both are constants - EqualsInternal will handle it just fine if (RuntimeHelpers.IsKnownConstant(a)) { @@ -707,43 +713,87 @@ static bool EqualsUnrolled(string? a, string b) if (b.Length == 0) { - return a != null && a.Length == 0; + return a.Length == 0; } if (b.Length == 1) { - return a != null && - // Length: [ 0 ][ 1 ], ch1: [ X ][ \0 ] - we can compare Length and firstChar using a single - // cmp operation, it's safe because there is also '\0' char we can rely on - Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == - (((ulong)b[0] << 32) | 1UL); + // Length: [ 0 ][ 1 ], ch1: [ X ][ \0 ] - we can compare Length and firstChar using a single + // cmp operation, it's safe because there is also '\0' char we can rely on + return Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == + (((ulong)b[0] << 32) | 1UL); } if (b.Length == 2) { // Same here, compare Length and two chars in a single cmp - return a != null && - Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == - (((ulong)b[1] << 48) | ((ulong)b[0] << 32) | 2UL); + return Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == + (((ulong)b[1] << 48) | ((ulong)b[0] << 32) | 2UL); } if (b.Length == 3) { // it's safe to load 64bit here because of '\0' - return a != null && a.Length == 3 && - Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == - (((ulong)b[2] << 32) | ((ulong)b[1] << 16) | (ulong)b[0]); + return a.Length == 3 && Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == + (((ulong)b[2] << 32) | ((ulong)b[1] << 16) | (ulong)b[0]); } if (b.Length == 4) { - return a != null && a.Length == 4 && - Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == - (((ulong)b[3] << 48) | ((ulong)b[2] << 32) | ((ulong)b[1] << 16) | b[0]); + return a.Length == 4 && Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == + (((ulong)b[3] << 48) | ((ulong)b[2] << 32) | ((ulong)b[1] << 16) | b[0]); } return EqualsInternal(a, b); } + + /* + + // Vectorized unrolling demo: + + if (RuntimeHelpers.IsKnownConstant(b) && b != null && b.Length >= 8 && b.Length <= 10) // TODO: 16 + { + return EqualsUnrolled_8_to_16_Vector128(a, b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool EqualsUnrolled_8_to_16_Vector128(string? a, string b) + { + // if both are constants - EqualsInternal will handle it just fine + if (a == null) + { + return false; // b is not null + } + + // if both are constants - EqualsInternal will handle it just fine + if (RuntimeHelpers.IsKnownConstant(a)) + { + return EqualsInternal(a, b); + } + + Vector128 v1 = Unsafe.ReadUnaligned>( + ref Unsafe.As(ref a._firstChar)); + Vector128 v2 = Unsafe.ReadUnaligned>( + ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, a.Length - 16))); + + Vector128 constV1 = Vector128.Create(b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); + Vector128 constV2 = default; + + if (b.Length == 8) constV2 = constV1; // TODO: for len=8 we don't need the 2nd vector at all + if (b.Length == 9) constV2 = Vector128.Create(b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]); + if (b.Length == 10) constV2 = Vector128.Create(b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9]); + if (b.Length == 11) constV2 = Vector128.Create(b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10]); + if (b.Length == 12) constV2 = Vector128.Create(b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11]); + if (b.Length == 13) constV2 = Vector128.Create(b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12]; + if (b.Length == 14) constV2 = Vector128.Create(b[6], b[7], b[8], b[9], b[10], b[11], b[10], b[13]); + if (b.Length == 15) constV2 = Vector128.Create(b[7], b[8], b[9], b[10], b[11], b[10], b[11]), b[14]); + + // 16 can be done via a single Vector256 + if (b.Length == 16) constV2 = Vector128.Create(b[8], b[9], b[10], b[11], b[10], b[11]), b[11]), b[15]); + + return ((v1 ^ constV1) | (v2 ^ constV2)) == Vector128.Zero; + } + */ #endif return EqualsInternal(a, b); From 60fb02f31365918b100b255769dfffc60801486f Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 04:45:14 +0300 Subject: [PATCH 04/19] better codegen --- .../src/System/String.Comparison.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 6b787a5ff88a9a..9ce3747523c2f4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -700,11 +700,6 @@ public static bool Equals(string? a, string? b) [MethodImpl(MethodImplOptions.AggressiveInlining)] static bool EqualsUnrolled(string? a, string b) { - if (a == null) - { - return false; // b is not null - } - // if both are constants - EqualsInternal will handle it just fine if (RuntimeHelpers.IsKnownConstant(a)) { @@ -713,34 +708,34 @@ static bool EqualsUnrolled(string? a, string b) if (b.Length == 0) { - return a.Length == 0; + return a != null && a.Length == 0; } if (b.Length == 1) { // Length: [ 0 ][ 1 ], ch1: [ X ][ \0 ] - we can compare Length and firstChar using a single // cmp operation, it's safe because there is also '\0' char we can rely on - return Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == + return a != null && Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == (((ulong)b[0] << 32) | 1UL); } if (b.Length == 2) { // Same here, compare Length and two chars in a single cmp - return Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == + return a != null && Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == (((ulong)b[1] << 48) | ((ulong)b[0] << 32) | 2UL); } if (b.Length == 3) { // it's safe to load 64bit here because of '\0' - return a.Length == 3 && Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == + return a?.Length == 3 && Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == (((ulong)b[2] << 32) | ((ulong)b[1] << 16) | (ulong)b[0]); } if (b.Length == 4) { - return a.Length == 4 && Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == + return a?.Length == 4 && Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == (((ulong)b[3] << 48) | ((ulong)b[2] << 32) | ((ulong)b[1] << 16) | b[0]); } From 23f5a29f10794af8f5cdc5ef01b1e7a2bba93b7b Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 04:52:55 +0300 Subject: [PATCH 05/19] Reformat --- .../src/System/String.Comparison.cs | 87 +++++++++---------- 1 file changed, 41 insertions(+), 46 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 9ce3747523c2f4..2dc2e97da1622a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -742,53 +742,48 @@ static bool EqualsUnrolled(string? a, string b) return EqualsInternal(a, b); } - /* + // // Vectorized unrolling demo: + + // if (RuntimeHelpers.IsKnownConstant(b) && b != null && b.Length >= 8 && b.Length <= 16) // TODO: 16 + // { + // return EqualsUnrolled_8_to_16_Vector128(a, b); + // } + + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // static bool EqualsUnrolled_8_to_16_Vector128(string? a, string b) + // { + // // if both are constants - EqualsInternal will handle it just fine + // if (a == null) + // { + // return false; // b is not null + // } + + // // if both are constants - EqualsInternal will handle it just fine + // if (RuntimeHelpers.IsKnownConstant(a)) + // { + // return EqualsInternal(a, b); + // } + + // Vector128 constV1 = Vector128.Create(b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); + // Vector128 constV2 = default; + + // if (b.Length == 8) constV2 = constV1; + // if (b.Length == 9) constV2 = Vector128.Create(b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]); + // if (b.Length == 10) constV2 = Vector128.Create(b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9]); + // if (b.Length == 11) constV2 = Vector128.Create(b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10]); + // if (b.Length == 12) constV2 = Vector128.Create(b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11]); + // if (b.Length == 13) constV2 = Vector128.Create(b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12]; + // if (b.Length == 14) constV2 = Vector128.Create(b[6], b[7], b[8], b[9], b[10], b[11], b[10], b[13]); + // if (b.Length == 15) constV2 = Vector128.Create(b[7], b[8], b[9], b[10], b[11], b[10], b[11]), b[14]); + // if (b.Length == 16) constV2 = Vector128.Create(b[8], b[9], b[10], b[11], b[10], b[11]), b[11]), b[15]); + + // // TODO: len == 8 and len == 16 can be special cased + + // return ((Unsafe.ReadUnaligned>(ref Unsafe.As(ref a._firstChar)) ^ constV1) | + // (Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, a.Length - 16))) ^ constV2)) == + // Vector128.Zero; + // } - // Vectorized unrolling demo: - - if (RuntimeHelpers.IsKnownConstant(b) && b != null && b.Length >= 8 && b.Length <= 10) // TODO: 16 - { - return EqualsUnrolled_8_to_16_Vector128(a, b); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool EqualsUnrolled_8_to_16_Vector128(string? a, string b) - { - // if both are constants - EqualsInternal will handle it just fine - if (a == null) - { - return false; // b is not null - } - - // if both are constants - EqualsInternal will handle it just fine - if (RuntimeHelpers.IsKnownConstant(a)) - { - return EqualsInternal(a, b); - } - - Vector128 v1 = Unsafe.ReadUnaligned>( - ref Unsafe.As(ref a._firstChar)); - Vector128 v2 = Unsafe.ReadUnaligned>( - ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, a.Length - 16))); - - Vector128 constV1 = Vector128.Create(b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); - Vector128 constV2 = default; - - if (b.Length == 8) constV2 = constV1; // TODO: for len=8 we don't need the 2nd vector at all - if (b.Length == 9) constV2 = Vector128.Create(b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]); - if (b.Length == 10) constV2 = Vector128.Create(b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9]); - if (b.Length == 11) constV2 = Vector128.Create(b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10]); - if (b.Length == 12) constV2 = Vector128.Create(b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11]); - if (b.Length == 13) constV2 = Vector128.Create(b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12]; - if (b.Length == 14) constV2 = Vector128.Create(b[6], b[7], b[8], b[9], b[10], b[11], b[10], b[13]); - if (b.Length == 15) constV2 = Vector128.Create(b[7], b[8], b[9], b[10], b[11], b[10], b[11]), b[14]); - - // 16 can be done via a single Vector256 - if (b.Length == 16) constV2 = Vector128.Create(b[8], b[9], b[10], b[11], b[10], b[11]), b[11]), b[15]); - - return ((v1 ^ constV1) | (v2 ^ constV2)) == Vector128.Zero; - } - */ #endif return EqualsInternal(a, b); From 6bdd6b02a1599904467a35f24a541774f75fbbfe Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 15:49:56 +0300 Subject: [PATCH 06/19] Inliner's friendly version --- src/coreclr/jit/morph.cpp | 6 + .../src/System/String.Comparison.cs | 159 +++++++++--------- 2 files changed, 87 insertions(+), 78 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index b3e9e8fa82977f..f8d4a141b44beb 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5183,6 +5183,12 @@ GenTree* Compiler::fgMorphArrayIndex(GenTree* tree) noway_assert(elemTyp != TYP_STRUCT || elemStructType != nullptr); + if (opts.OptimizationEnabled()) + { + // Fold possible constant expressions + asIndex->Index() = gtFoldExpr(asIndex->Index()); + } + // Fold "cns_str"[cns_index] to ushort constant // NOTE: don't do it for empty string, the operation will fail anyway if (opts.OptimizationEnabled() && asIndex->Arr()->OperIs(GT_CNS_STR) && diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 2dc2e97da1622a..0f562f0383a1e9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -687,103 +687,106 @@ public bool Equals([NotNullWhen(true)] string? value, StringComparison compariso public static bool Equals(string? a, string? b) { #if TARGET_64BIT && !MONO && !BIGENDIAN - if (RuntimeHelpers.IsKnownConstant(b) && b != null && b.Length <= 4) + if (RuntimeHelpers.IsKnownConstant(b) && !RuntimeHelpers.IsKnownConstant(a) && + b != null && b.Length <= 4) { return EqualsUnrolled(a, b); } - if (RuntimeHelpers.IsKnownConstant(a) && a != null && a.Length <= 4) - { - return EqualsUnrolled(b, a); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] static bool EqualsUnrolled(string? a, string b) { - // if both are constants - EqualsInternal will handle it just fine - if (RuntimeHelpers.IsKnownConstant(a)) + if (a != null) { - return EqualsInternal(a, b); - } + if (b.Length == 0) + { + // Fold Equals(a, "") to 'a != null && a.Length == 0' + return a.Length == 0; + } - if (b.Length == 0) - { - return a != null && a.Length == 0; - } + // String's layout: + // + // bytes: [ ][ ][ ][ ][ ][ ][ ][ ] ... [ ][ ][ ][ ] + // [ Length ][ c1 ][ c2 ] ... [ cN ][ \0 ] + // + // We can always rely on 2 bytes representing '\0' at the end - if (b.Length == 1) - { - // Length: [ 0 ][ 1 ], ch1: [ X ][ \0 ] - we can compare Length and firstChar using a single - // cmp operation, it's safe because there is also '\0' char we can rely on - return a != null && Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == - (((ulong)b[0] << 32) | 1UL); - } + if (b.Length == 1) + { + // Compare Length, firstChar and '\0' using a single 64bit cmp + return + Unsafe.ReadUnaligned( + ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == + (((ulong)b[0] << 32) | 1UL); + } - if (b.Length == 2) - { - // Same here, compare Length and two chars in a single cmp - return a != null && Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == - (((ulong)b[1] << 48) | ((ulong)b[0] << 32) | 2UL); - } + if (b.Length == 2) + { + // Compare Length, firstChar, secondChar using a single 64bit cmp + return + Unsafe.ReadUnaligned( + ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == + (((ulong)b[1] << 48) | ((ulong)b[0] << 32) | 2UL); + } - if (b.Length == 3) - { - // it's safe to load 64bit here because of '\0' - return a?.Length == 3 && Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == - (((ulong)b[2] << 32) | ((ulong)b[1] << 16) | (ulong)b[0]); + if (b.Length == 3) + { + // it's safe to load 64bit here because of '\0' + return a.Length == 3 && + Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == + (((ulong)b[2] << 32) | ((ulong)b[1] << 16) | (ulong)b[0]); + } + + //Debug.Assert(b.Length == 4); + + return a.Length == 4 && + Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == + (((ulong)b[3] << 48) | ((ulong)b[2] << 32) | ((ulong)b[1] << 16) | b[0]); } - if (b.Length == 4) + // a is null when b is a known non-null + return false; + } + + // Vectorized unrolling demo: + + if (RuntimeHelpers.IsKnownConstant(b) && b != null && b.Length >= 8 && b.Length <= 16) // TODO: 16 + { + return EqualsUnrolled_8_to_16_Vector128(a, b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool EqualsUnrolled_8_to_16_Vector128(string? a, string b) + { + if (a != null) { - return a?.Length == 4 && Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == - (((ulong)b[3] << 48) | ((ulong)b[2] << 32) | ((ulong)b[1] << 16) | b[0]); + // if both are constants - EqualsInternal will handle it just fine + if (RuntimeHelpers.IsKnownConstant(a)) + return EqualsInternal(a, b); + + // Load 'a' into two vectors with overlapping. + // TODO: special case len == 7, 8 (single V128 operation) and len == 15, 16 (single V256 operation) + // for len = 7 and len 15 we can rely on '\0' + // However, these special cases might eat inliner's budget so we need to fix that first + Vector128 v2 = Vector128.LoadUnsafe( + ref Unsafe.As(ref a._firstChar), (nuint)a.Length - 16).AsUInt16(); + Vector128 v1 = Vector128.LoadUnsafe( + ref Unsafe.As(ref a._firstChar)).AsUInt16(); + + // ((v1 ^ cns1) | (v2 ^ cns2)) == zero + return ((v1 ^ Vector128.Create(b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7])) | + (v2 ^ Vector128.Create( + b[b.Length - 8], b[b.Length - 7], + b[b.Length - 6], b[b.Length - 5], + b[b.Length - 4], b[b.Length - 3], + b[b.Length - 2], b[b.Length - 1]))) == + Vector128.Zero; } - return EqualsInternal(a, b); + // a is null when b is a known non-null + return false; } - // // Vectorized unrolling demo: - - // if (RuntimeHelpers.IsKnownConstant(b) && b != null && b.Length >= 8 && b.Length <= 16) // TODO: 16 - // { - // return EqualsUnrolled_8_to_16_Vector128(a, b); - // } - - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // static bool EqualsUnrolled_8_to_16_Vector128(string? a, string b) - // { - // // if both are constants - EqualsInternal will handle it just fine - // if (a == null) - // { - // return false; // b is not null - // } - - // // if both are constants - EqualsInternal will handle it just fine - // if (RuntimeHelpers.IsKnownConstant(a)) - // { - // return EqualsInternal(a, b); - // } - - // Vector128 constV1 = Vector128.Create(b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); - // Vector128 constV2 = default; - - // if (b.Length == 8) constV2 = constV1; - // if (b.Length == 9) constV2 = Vector128.Create(b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]); - // if (b.Length == 10) constV2 = Vector128.Create(b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9]); - // if (b.Length == 11) constV2 = Vector128.Create(b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10]); - // if (b.Length == 12) constV2 = Vector128.Create(b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11]); - // if (b.Length == 13) constV2 = Vector128.Create(b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12]; - // if (b.Length == 14) constV2 = Vector128.Create(b[6], b[7], b[8], b[9], b[10], b[11], b[10], b[13]); - // if (b.Length == 15) constV2 = Vector128.Create(b[7], b[8], b[9], b[10], b[11], b[10], b[11]), b[14]); - // if (b.Length == 16) constV2 = Vector128.Create(b[8], b[9], b[10], b[11], b[10], b[11]), b[11]), b[15]); - - // // TODO: len == 8 and len == 16 can be special cased - - // return ((Unsafe.ReadUnaligned>(ref Unsafe.As(ref a._firstChar)) ^ constV1) | - // (Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, a.Length - 16))) ^ constV2)) == - // Vector128.Zero; - // } - #endif return EqualsInternal(a, b); From 3257a513bd93db2a689bb0a91faeab6bfec34e2f Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 16:48:54 +0300 Subject: [PATCH 07/19] Clean up --- .../src/System/String.Comparison.cs | 84 +++++++++---------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 0f562f0383a1e9..c856d31aaf1d9d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -682,28 +682,39 @@ public bool Equals([NotNullWhen(true)] string? value, StringComparison compariso } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong ReadUInt64(string str) => + Unsafe.ReadUnaligned(ref Unsafe.As(ref str._firstChar)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong ReadUInt64(string str, nint offset) => + Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref str._firstChar, offset))); + // Determines whether two Strings match. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Equals(string? a, string? b) { #if TARGET_64BIT && !MONO && !BIGENDIAN - if (RuntimeHelpers.IsKnownConstant(b) && !RuntimeHelpers.IsKnownConstant(a) && - b != null && b.Length <= 4) + // Try to unroll Equals in case of constant 'b' for b.Length in [0..16] range + if (RuntimeHelpers.IsKnownConstant(b) && !RuntimeHelpers.IsKnownConstant(a) && b != null) { - return EqualsUnrolled(a, b); + // Unroll using SWAR + if (b.Length <= 8) + { + return EqualsUnrolled_0_to_8(a, b); + } + // Unroll using Vector128 (SSE or AdvSimd) + if (b.Length >= 9 && b.Length <= 16 && Vector128.IsHardwareAccelerated) + { + return EqualsUnrolled_9_to_16(a, b); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool EqualsUnrolled(string? a, string b) + static bool EqualsUnrolled_0_to_8(string? a, string b) { if (a != null) { - if (b.Length == 0) - { - // Fold Equals(a, "") to 'a != null && a.Length == 0' - return a.Length == 0; - } - // String's layout: // // bytes: [ ][ ][ ][ ][ ][ ][ ][ ] ... [ ][ ][ ][ ] @@ -711,59 +722,47 @@ static bool EqualsUnrolled(string? a, string b) // // We can always rely on 2 bytes representing '\0' at the end + // Fold Equals(a, "") to 'a != null && a.Length == 0' + if (b.Length == 0) + { + return a.Length == 0; + } if (b.Length == 1) { // Compare Length, firstChar and '\0' using a single 64bit cmp - return - Unsafe.ReadUnaligned( - ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == - (((ulong)b[0] << 32) | 1UL); + return ReadUInt64(a, -2) == (((ulong)b[0] << 32) | 1UL); } - if (b.Length == 2) { // Compare Length, firstChar, secondChar using a single 64bit cmp - return - Unsafe.ReadUnaligned( - ref Unsafe.As(ref Unsafe.Add(ref a._firstChar, -2))) == - (((ulong)b[1] << 48) | ((ulong)b[0] << 32) | 2UL); + return ReadUInt64(a, -2) == (((ulong)b[1] << 48) | ((ulong)b[0] << 32) | 2UL); } - if (b.Length == 3) { // it's safe to load 64bit here because of '\0' - return a.Length == 3 && - Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == - (((ulong)b[2] << 32) | ((ulong)b[1] << 16) | (ulong)b[0]); + return a.Length == 3 && ReadUInt64(a) == (((ulong)b[2] << 32) | ((ulong)b[1] << 16) | b[0]); } - //Debug.Assert(b.Length == 4); - - return a.Length == 4 && - Unsafe.ReadUnaligned(ref Unsafe.As(ref a._firstChar)) == - (((ulong)b[3] << 48) | ((ulong)b[2] << 32) | ((ulong)b[1] << 16) | b[0]); + ulong v1 = ReadUInt64(a); + ulong cns1 = ((ulong)b[3] << 48) | ((ulong)b[2] << 32) | ((ulong)b[1] << 16) | b[0]; + if (b.Length == 4) + { + return a.Length == 4 && v1 == cns1; + } + // Handle Length [5..8] via two ulong (overlapped) + return v1 == cns1 && ReadUInt64(a, a.Length - 4) == + (((ulong)b[b.Length - 1] << 48) | ((ulong)b[b.Length - 2] << 32) | ((ulong)b[b.Length - 3] << 16) | b[b.Length - 4]); } // a is null when b is a known non-null return false; } - // Vectorized unrolling demo: - - if (RuntimeHelpers.IsKnownConstant(b) && b != null && b.Length >= 8 && b.Length <= 16) // TODO: 16 - { - return EqualsUnrolled_8_to_16_Vector128(a, b); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool EqualsUnrolled_8_to_16_Vector128(string? a, string b) + static bool EqualsUnrolled_9_to_16(string? a, string b) { if (a != null) { - // if both are constants - EqualsInternal will handle it just fine - if (RuntimeHelpers.IsKnownConstant(a)) - return EqualsInternal(a, b); - // Load 'a' into two vectors with overlapping. // TODO: special case len == 7, 8 (single V128 operation) and len == 15, 16 (single V256 operation) // for len = 7 and len 15 we can rely on '\0' @@ -786,11 +785,10 @@ static bool EqualsUnrolled_8_to_16_Vector128(string? a, string b) // a is null when b is a known non-null return false; } - #endif - return EqualsInternal(a, b); + return EqualsFallback(a, b); - static bool EqualsInternal(string? a, string? b) + static bool EqualsFallback(string? a, string? b) { if (object.ReferenceEquals(a, b)) { From fb9b881d0509dca2af15162a7d87bf0b67eb658d Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 17:49:12 +0300 Subject: [PATCH 08/19] Fix SIMD impl, clean up. Remove StartsWith for now - leaving it up-for-grabs as a follow up --- .../src/System/String.Comparison.cs | 152 ++++++------------ 1 file changed, 50 insertions(+), 102 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index c856d31aaf1d9d..1571f448cd3705 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -703,11 +703,14 @@ public static bool Equals(string? a, string? b) { return EqualsUnrolled_0_to_8(a, b); } - // Unroll using Vector128 (SSE or AdvSimd) + // Unroll using two Vector128s if (b.Length >= 9 && b.Length <= 16 && Vector128.IsHardwareAccelerated) { return EqualsUnrolled_9_to_16(a, b); } + // NOTE: for some values we can emit a more optimal codegen e.g. for Length 7 and 8 + // we can use a single Vector128 or add Vector256 path for Length in [16..32] range + // but we need to be careful here with inliner's budget } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -715,31 +718,24 @@ static bool EqualsUnrolled_0_to_8(string? a, string b) { if (a != null) { - // String's layout: - // - // bytes: [ ][ ][ ][ ][ ][ ][ ][ ] ... [ ][ ][ ][ ] - // [ Length ][ c1 ][ c2 ] ... [ cN ][ \0 ] - // - // We can always rely on 2 bytes representing '\0' at the end - - // Fold Equals(a, "") to 'a != null && a.Length == 0' if (b.Length == 0) { + // Fold Equals(a, "") to 'a != null && a.Length == 0' return a.Length == 0; } if (b.Length == 1) { - // Compare Length, firstChar and '\0' using a single 64bit cmp + // Load Length, ch1, \0 into ulong (so we can skip the Length check) return ReadUInt64(a, -2) == (((ulong)b[0] << 32) | 1UL); } if (b.Length == 2) { - // Compare Length, firstChar, secondChar using a single 64bit cmp + // Load Length, ch1, ch2 into ulong (so we can skip the Length check) return ReadUInt64(a, -2) == (((ulong)b[1] << 48) | ((ulong)b[0] << 32) | 2UL); } if (b.Length == 3) { - // it's safe to load 64bit here because of '\0' + // Load ch1, ch2, ch3 and \0 into ulong return a.Length == 3 && ReadUInt64(a) == (((ulong)b[2] << 32) | ((ulong)b[1] << 16) | b[0]); } @@ -747,6 +743,7 @@ static bool EqualsUnrolled_0_to_8(string? a, string b) ulong cns1 = ((ulong)b[3] << 48) | ((ulong)b[2] << 32) | ((ulong)b[1] << 16) | b[0]; if (b.Length == 4) { + // Load ch1, ch2, ch3 and ch4 into ulong return a.Length == 4 && v1 == cns1; } // Handle Length [5..8] via two ulong (overlapped) @@ -764,27 +761,21 @@ static bool EqualsUnrolled_9_to_16(string? a, string b) if (a != null) { // Load 'a' into two vectors with overlapping. - // TODO: special case len == 7, 8 (single V128 operation) and len == 15, 16 (single V256 operation) - // for len = 7 and len 15 we can rely on '\0' - // However, these special cases might eat inliner's budget so we need to fix that first - Vector128 v2 = Vector128.LoadUnsafe( - ref Unsafe.As(ref a._firstChar), (nuint)a.Length - 16).AsUInt16(); - Vector128 v1 = Vector128.LoadUnsafe( - ref Unsafe.As(ref a._firstChar)).AsUInt16(); + Vector128 v2 = Vector128.LoadUnsafe(ref Unsafe.As(ref a._firstChar), (nuint)a.Length - 8); + Vector128 v1 = Vector128.LoadUnsafe(ref Unsafe.As(ref a._firstChar)); // ((v1 ^ cns1) | (v2 ^ cns2)) == zero return ((v1 ^ Vector128.Create(b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7])) | (v2 ^ Vector128.Create( - b[b.Length - 8], b[b.Length - 7], - b[b.Length - 6], b[b.Length - 5], - b[b.Length - 4], b[b.Length - 3], - b[b.Length - 2], b[b.Length - 1]))) == - Vector128.Zero; + b[b.Length - 8], b[b.Length - 7], b[b.Length - 6], b[b.Length - 5], + b[b.Length - 4], b[b.Length - 3], b[b.Length - 2], b[b.Length - 1]))) == Vector128.Zero; } - // a is null when b is a known non-null return false; } + + // Two-Vector256s-impl can be basically a copy-paste of ^ with wider vectors to handle inputs [17..32] + // but we need to tune inliner's budget first #endif return EqualsFallback(a, b); @@ -1061,92 +1052,49 @@ public bool StartsWith(string value, StringComparison comparisonType) throw new ArgumentNullException(nameof(value)); } -#if TARGET_64BIT && !MONO && !BIGENDIAN - if (RuntimeHelpers.IsKnownConstant(value) && RuntimeHelpers.IsKnownConstant((int)comparisonType) && - comparisonType == StringComparison.Ordinal && value.Length <= 4) + if ((object)this == (object)value) { - return StartsWithUnrolledOrdinal(value); + CheckStringComparison(comparisonType); + return true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - bool StartsWithUnrolledOrdinal(string value) + if (value.Length == 0) { - if (value.Length == 0) - { - return true; - } - if (value.Length == 1) - { - return _stringLength >= 1 && _firstChar == value[0]; - } - if (value.Length == 2) - { - return _stringLength >= 2 && Unsafe.ReadUnaligned(ref Unsafe.As(ref _firstChar)) == - (((uint)value[1] << 16) | value[0]); - } - if (value.Length == 3) - { - // it's safe to load 64bit here because of '\0' but we need to mask the last char out - return _stringLength >= 3 && Unsafe.ReadUnaligned(ref Unsafe.As(ref _firstChar)) << 16 == - (((ulong)value[2] << 48) | ((ulong)value[1] << 32) | (ulong)value[0] << 16); - } - if (value.Length == 4) - { - return _stringLength >= 4 && Unsafe.ReadUnaligned(ref Unsafe.As(ref _firstChar)) == - (((ulong)value[3] << 48) | ((ulong)value[2] << 32) | ((ulong)value[1] << 16) | value[0]); - } - return StartsWithInternal(value, StringComparison.Ordinal); + CheckStringComparison(comparisonType); + return true; } -#endif - - return StartsWithInternal(value, comparisonType); - bool StartsWithInternal(string value, StringComparison comparisonType) + switch (comparisonType) { - if ((object)this == (object)value) - { - CheckStringComparison(comparisonType); - return true; - } + case StringComparison.CurrentCulture: + case StringComparison.CurrentCultureIgnoreCase: + return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, GetCaseCompareOfComparisonCulture(comparisonType)); - if (value.Length == 0) - { - CheckStringComparison(comparisonType); - return true; - } + case StringComparison.InvariantCulture: + case StringComparison.InvariantCultureIgnoreCase: + return CompareInfo.Invariant.IsPrefix(this, value, GetCaseCompareOfComparisonCulture(comparisonType)); - switch (comparisonType) - { - case StringComparison.CurrentCulture: - case StringComparison.CurrentCultureIgnoreCase: - return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, GetCaseCompareOfComparisonCulture(comparisonType)); - - case StringComparison.InvariantCulture: - case StringComparison.InvariantCultureIgnoreCase: - return CompareInfo.Invariant.IsPrefix(this, value, GetCaseCompareOfComparisonCulture(comparisonType)); - - case StringComparison.Ordinal: - if (this.Length < value.Length || _firstChar != value._firstChar) - { - return false; - } - return (value.Length == 1) ? - true : // First char is the same and thats all there is to compare - SpanHelpers.SequenceEqual( - ref Unsafe.As(ref this.GetRawStringData()), - ref Unsafe.As(ref value.GetRawStringData()), - ((nuint)value.Length) * 2); - - case StringComparison.OrdinalIgnoreCase: - if (this.Length < value.Length) - { - return false; - } - return Ordinal.EqualsIgnoreCase(ref this.GetRawStringData(), ref value.GetRawStringData(), value.Length); - - default: - throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); - } + case StringComparison.Ordinal: + if (this.Length < value.Length || _firstChar != value._firstChar) + { + return false; + } + return (value.Length == 1) ? + true : // First char is the same and thats all there is to compare + SpanHelpers.SequenceEqual( + ref Unsafe.As(ref this.GetRawStringData()), + ref Unsafe.As(ref value.GetRawStringData()), + ((nuint)value.Length) * 2); + + case StringComparison.OrdinalIgnoreCase: + if (this.Length < value.Length) + { + return false; + } + return Ordinal.EqualsIgnoreCase(ref this.GetRawStringData(), ref value.GetRawStringData(), value.Length); + + default: + throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); } } From 3097c5be18cb1fa91ab054b9d003c1950018b77b Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 17:50:40 +0300 Subject: [PATCH 09/19] Clean up again --- .../src/System/Runtime/CompilerServices/RuntimeHelpers.cs | 3 --- .../System.Private.CoreLib/src/System/String.Comparison.cs | 2 -- 2 files changed, 5 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 9c57a49bf2b0a5..498055ad6a1c51 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -127,8 +127,5 @@ internal static bool IsPrimitiveType(this CorElementType et) [Intrinsic] internal static bool IsKnownConstant(char t) => false; - - [Intrinsic] - internal static bool IsKnownConstant(int t) => false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 1571f448cd3705..c93d022fd0de9e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -1043,8 +1043,6 @@ public bool StartsWith(string value) return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, GetCaseCompareOfComparisonCulture(StringComparison.CurrentCulture)); } - // A hint for the inliner to always prescan this method - // and take the constant input path public bool StartsWith(string value, StringComparison comparisonType) { if (value is null) From 05ccb495b38ff4614517fd32a1f61c65f8fbabbc Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 20:12:13 +0300 Subject: [PATCH 10/19] Address feedback --- .../src/System/String.Comparison.cs | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index c93d022fd0de9e..7fc78d276228dd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -731,16 +731,22 @@ static bool EqualsUnrolled_0_to_8(string? a, string b) if (b.Length == 2) { // Load Length, ch1, ch2 into ulong (so we can skip the Length check) - return ReadUInt64(a, -2) == (((ulong)b[1] << 48) | ((ulong)b[0] << 32) | 2UL); + return a.Length == 2 && ReadUInt64(a, -2) == (((ulong)b[1] << 48) | + ((ulong)b[0] << 32) | 2UL); } if (b.Length == 3) { // Load ch1, ch2, ch3 and \0 into ulong - return a.Length == 3 && ReadUInt64(a) == (((ulong)b[2] << 32) | ((ulong)b[1] << 16) | b[0]); + return a.Length == 3 && + ReadUInt64(a) == (((ulong)b[2] << 32) | + ((ulong)b[1] << 16) | b[0]); } ulong v1 = ReadUInt64(a); - ulong cns1 = ((ulong)b[3] << 48) | ((ulong)b[2] << 32) | ((ulong)b[1] << 16) | b[0]; + ulong cns1 = ((ulong)b[3] << 48) | + ((ulong)b[2] << 32) | + ((ulong)b[1] << 16) | b[0]; + if (b.Length == 4) { // Load ch1, ch2, ch3 and ch4 into ulong @@ -748,7 +754,10 @@ static bool EqualsUnrolled_0_to_8(string? a, string b) } // Handle Length [5..8] via two ulong (overlapped) return v1 == cns1 && ReadUInt64(a, a.Length - 4) == - (((ulong)b[b.Length - 1] << 48) | ((ulong)b[b.Length - 2] << 32) | ((ulong)b[b.Length - 3] << 16) | b[b.Length - 4]); + (((ulong)b[b.Length - 1] << 48) | + ((ulong)b[b.Length - 2] << 32) | + ((ulong)b[b.Length - 3] << 16) | + ((ulong)b[b.Length - 4] << 0)); } // a is null when b is a known non-null @@ -758,17 +767,24 @@ static bool EqualsUnrolled_0_to_8(string? a, string b) [MethodImpl(MethodImplOptions.AggressiveInlining)] static bool EqualsUnrolled_9_to_16(string? a, string b) { - if (a != null) + if (a?.Length >= b.Length) { // Load 'a' into two vectors with overlapping. - Vector128 v2 = Vector128.LoadUnsafe(ref Unsafe.As(ref a._firstChar), (nuint)a.Length - 8); - Vector128 v1 = Vector128.LoadUnsafe(ref Unsafe.As(ref a._firstChar)); + Vector128 v2 = Vector128.LoadUnsafe( + ref Unsafe.As(ref a._firstChar), (nuint)a.Length - 8); + Vector128 v1 = Vector128.LoadUnsafe( + ref Unsafe.As(ref a._firstChar)); // ((v1 ^ cns1) | (v2 ^ cns2)) == zero - return ((v1 ^ Vector128.Create(b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7])) | + return ((v1 ^ Vector128.Create( + b[0], b[1], b[2], b[3], + b[4], b[5], b[6], b[7])) | (v2 ^ Vector128.Create( - b[b.Length - 8], b[b.Length - 7], b[b.Length - 6], b[b.Length - 5], - b[b.Length - 4], b[b.Length - 3], b[b.Length - 2], b[b.Length - 1]))) == Vector128.Zero; + // b[b.Length - c] are folded to constants + b[b.Length - 8], b[b.Length - 7], + b[b.Length - 6], b[b.Length - 5], + b[b.Length - 4], b[b.Length - 3], + b[b.Length - 2], b[b.Length - 1]))) == Vector128.Zero; } // a is null when b is a known non-null return false; From b8382157af5306ca7efa82749d2ec736afce7149 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Sat, 5 Feb 2022 20:12:40 +0300 Subject: [PATCH 11/19] Update src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- .../System.Private.CoreLib/src/System/String.Comparison.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 7fc78d276228dd..0ced2dc936c69d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -704,7 +704,7 @@ public static bool Equals(string? a, string? b) return EqualsUnrolled_0_to_8(a, b); } // Unroll using two Vector128s - if (b.Length >= 9 && b.Length <= 16 && Vector128.IsHardwareAccelerated) + if (b.Length <= 16 && Vector128.IsHardwareAccelerated) { return EqualsUnrolled_9_to_16(a, b); } From 98175146e9e6fcd460ff4a854a8d8bf45f7b1765 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 20:24:06 +0300 Subject: [PATCH 12/19] Add a comment --- .../src/System/String.Comparison.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 7fc78d276228dd..f7725682622c31 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -723,17 +723,20 @@ static bool EqualsUnrolled_0_to_8(string? a, string b) // Fold Equals(a, "") to 'a != null && a.Length == 0' return a.Length == 0; } + + // For Length 1 and 2 we rely on fact that even an empty string is 8 bytes long (on 64bit) + // [ 4b Length ][ 2b _firstChar ][ 2b padding ] + // so we can check Length and first 2 chars in a single operation if (b.Length == 1) { - // Load Length, ch1, \0 into ulong (so we can skip the Length check) return ReadUInt64(a, -2) == (((ulong)b[0] << 32) | 1UL); } if (b.Length == 2) { - // Load Length, ch1, ch2 into ulong (so we can skip the Length check) - return a.Length == 2 && ReadUInt64(a, -2) == (((ulong)b[1] << 48) | - ((ulong)b[0] << 32) | 2UL); + return ReadUInt64(a, -2) == (((ulong)b[1] << 48) | + ((ulong)b[0] << 32) | 2UL); } + if (b.Length == 3) { // Load ch1, ch2, ch3 and \0 into ulong From 49871fd097200c798c7a616bd8facbc372d86ad4 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 20:28:53 +0300 Subject: [PATCH 13/19] simple change, big codegen diff O_o --- .../System.Private.CoreLib/src/System/String.Comparison.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 9032936f3a30b2..563fd7940ddf3e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -770,7 +770,7 @@ static bool EqualsUnrolled_0_to_8(string? a, string b) [MethodImpl(MethodImplOptions.AggressiveInlining)] static bool EqualsUnrolled_9_to_16(string? a, string b) { - if (a?.Length >= b.Length) + if (a != null && a.Length >= b.Length) { // Load 'a' into two vectors with overlapping. Vector128 v2 = Vector128.LoadUnsafe( From 147647dec6d434c37e72447733fae48119f40b90 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Sat, 5 Feb 2022 21:52:24 +0300 Subject: [PATCH 14/19] Update src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs Co-authored-by: Miha Zupan --- .../System.Private.CoreLib/src/System/String.Comparison.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 563fd7940ddf3e..c58b9423dbcd73 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -770,7 +770,7 @@ static bool EqualsUnrolled_0_to_8(string? a, string b) [MethodImpl(MethodImplOptions.AggressiveInlining)] static bool EqualsUnrolled_9_to_16(string? a, string b) { - if (a != null && a.Length >= b.Length) + if (a != null && a.Length == b.Length) { // Load 'a' into two vectors with overlapping. Vector128 v2 = Vector128.LoadUnsafe( From 6f82123b407a1858589055aecbcba8fc90961b40 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 22:52:18 +0300 Subject: [PATCH 15/19] address feedback --- .../src/System/String.Comparison.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index c58b9423dbcd73..1c40a0b7913a71 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -755,12 +755,14 @@ static bool EqualsUnrolled_0_to_8(string? a, string b) // Load ch1, ch2, ch3 and ch4 into ulong return a.Length == 4 && v1 == cns1; } + // Handle Length [5..8] via two ulong (overlapped) - return v1 == cns1 && ReadUInt64(a, a.Length - 4) == - (((ulong)b[b.Length - 1] << 48) | - ((ulong)b[b.Length - 2] << 32) | - ((ulong)b[b.Length - 3] << 16) | - ((ulong)b[b.Length - 4] << 0)); + return a.Length == b.Length && v1 == cns1 && + ReadUInt64(a, b.Length - 4) == + (((ulong)b[b.Length - 1] << 48) | + ((ulong)b[b.Length - 2] << 32) | + ((ulong)b[b.Length - 3] << 16) | + ((ulong)b[b.Length - 4] << 0)); } // a is null when b is a known non-null @@ -774,7 +776,7 @@ static bool EqualsUnrolled_9_to_16(string? a, string b) { // Load 'a' into two vectors with overlapping. Vector128 v2 = Vector128.LoadUnsafe( - ref Unsafe.As(ref a._firstChar), (nuint)a.Length - 8); + ref Unsafe.As(ref a._firstChar), (nuint)b.Length - 8); Vector128 v1 = Vector128.LoadUnsafe( ref Unsafe.As(ref a._firstChar)); From 4b7b810fa9395e89de3bbc332de86997cfbc5530 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 23:05:20 +0300 Subject: [PATCH 16/19] Clean up --- .../src/System/String.Comparison.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 1c40a0b7913a71..e3362ae278ac4d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -757,12 +757,11 @@ static bool EqualsUnrolled_0_to_8(string? a, string b) } // Handle Length [5..8] via two ulong (overlapped) - return a.Length == b.Length && v1 == cns1 && - ReadUInt64(a, b.Length - 4) == - (((ulong)b[b.Length - 1] << 48) | - ((ulong)b[b.Length - 2] << 32) | - ((ulong)b[b.Length - 3] << 16) | - ((ulong)b[b.Length - 4] << 0)); + return a.Length == b.Length && v1 == cns1 && + ReadUInt64(a, b.Length - 4) == (((ulong)b[b.Length - 1] << 48) | + ((ulong)b[b.Length - 2] << 32) | + ((ulong)b[b.Length - 3] << 16) | + ((ulong)b[b.Length - 4] << 0)); } // a is null when b is a known non-null From 8cc4dc2b5ef15ca148e96a13f6582d5a39ef7e90 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 5 Feb 2022 23:33:46 +0300 Subject: [PATCH 17/19] only fold index expression in global morph --- src/coreclr/jit/morph.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index f8d4a141b44beb..355d603998242e 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5183,7 +5183,7 @@ GenTree* Compiler::fgMorphArrayIndex(GenTree* tree) noway_assert(elemTyp != TYP_STRUCT || elemStructType != nullptr); - if (opts.OptimizationEnabled()) + if (opts.OptimizationEnabled() && fgGlobalMorph) { // Fold possible constant expressions asIndex->Index() = gtFoldExpr(asIndex->Index()); From e0bd4ba45bc501cf3182cc1b335051917ad2a5bf Mon Sep 17 00:00:00 2001 From: EgorBo Date: Thu, 10 Feb 2022 04:58:32 +0300 Subject: [PATCH 18/19] Add tests --- .../src/System/String.Comparison.cs | 2 +- .../StringEqualsAutoVectorization.cs | 362 ++++++++++++++++++ .../StringEqualsAutoVectorization.csproj | 9 + 3 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 src/tests/JIT/opt/AutoVectorization/StringEqualsAutoVectorization.cs create mode 100644 src/tests/JIT/opt/AutoVectorization/StringEqualsAutoVectorization.csproj diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 4763f8ff896e47..fab6039b3c3f14 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -694,7 +694,7 @@ public static bool Equals(string? a, string? b) return EqualsUnrolled_0_to_8(a, b); } // Unroll using two Vector128s - if (b.Length <= 16 && Vector128.IsHardwareAccelerated) + else if (b.Length <= 16 && Vector128.IsHardwareAccelerated) { return EqualsUnrolled_9_to_16(a, b); } diff --git a/src/tests/JIT/opt/AutoVectorization/StringEqualsAutoVectorization.cs b/src/tests/JIT/opt/AutoVectorization/StringEqualsAutoVectorization.cs new file mode 100644 index 00000000000000..f818eb22ffcf07 --- /dev/null +++ b/src/tests/JIT/opt/AutoVectorization/StringEqualsAutoVectorization.cs @@ -0,0 +1,362 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; + +public class StringEqualsAutoVectorization +{ + public static int Main() + { + int testCount = 0; + foreach (var method in typeof(Tests).GetMethods()) + { + if (!method.Name.StartsWith("Equals_")) + continue; + + foreach (string testStr in Tests.s_TestData) + { + testCount++; + method.Invoke(null, new object[] { testStr }); + } + } + return testCount == 25920 ? 100 : 0; + } +} + +public static class Tests +{ + [MethodImpl(MethodImplOptions.NoInlining)] + static void ValidateEquals(bool result, string left, string right) + { + if (result != (left == right)) + throw new Exception($"{result} != ({left} == {right})"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_0(string s) => ValidateEquals(s == "", s, ""); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_1(string s) => ValidateEquals(s == "3", s, "3"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_2(string s) => ValidateEquals(s == "\0", s, "\0"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_3(string s) => ValidateEquals(s == "ж", s, "ж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_4(string s) => ValidateEquals(s == "1", s, "1"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_5(string s) => ValidateEquals(s == "33", s, "33"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_6(string s) => ValidateEquals(s == "31", s, "31"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_7(string s) => ValidateEquals(s == "a1", s, "a1"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_8(string s) => ValidateEquals(s == "12", s, "12"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_9(string s) => ValidateEquals(s == "1\0", s, "1\0"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_10(string s) => ValidateEquals(s == "b12", s, "b12"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_11(string s) => ValidateEquals(s == "ж23", s, "ж23"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_12(string s) => ValidateEquals(s == "2a2", s, "2a2"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_13(string s) => ValidateEquals(s == "222", s, "222"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_14(string s) => ValidateEquals(s == "0ь3", s, "0ь3"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_15(string s) => ValidateEquals(s == "bж31", s, "bж31"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_16(string s) => ValidateEquals(s == "ьЙbЙ", s, "ьЙbЙ"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_17(string s) => ValidateEquals(s == "b033", s, "b033"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_18(string s) => ValidateEquals(s == "311ь", s, "311ь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_19(string s) => ValidateEquals(s == "жЙ12", s, "жЙ12"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_20(string s) => ValidateEquals(s == "2011b", s, "2011b"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_21(string s) => ValidateEquals(s == "222b2", s, "222b2"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_22(string s) => ValidateEquals(s == "aЙ213", s, "aЙ213"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_23(string s) => ValidateEquals(s == "1a131", s, "1a131"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_24(string s) => ValidateEquals(s == "3232Й", s, "3232Й"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_25(string s) => ValidateEquals(s == "3b0ьжь", s, "3b0ьжь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_26(string s) => ValidateEquals(s == "213b2Й", s, "213b2Й"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_27(string s) => ValidateEquals(s == "b31210", s, "b31210"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_28(string s) => ValidateEquals(s == "1ж0021", s, "1ж0021"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_29(string s) => ValidateEquals(s == "3ь3112", s, "3ь3112"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_30(string s) => ValidateEquals(s == "122b231", s, "122b231"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_31(string s) => ValidateEquals(s == "03ж32ж3", s, "03ж32ж3"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_32(string s) => ValidateEquals(s == "bb31ж2Й", s, "bb31ж2Й"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_33(string s) => ValidateEquals(s == "023bьжЙ", s, "023bьжЙ"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_34(string s) => ValidateEquals(s == "\0232a12", s, "\0232a12"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_35(string s) => ValidateEquals(s == "ж13ь11Йь", s, "ж13ь11Йь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_36(string s) => ValidateEquals(s == "11ьbb32ь", s, "11ьbb32ь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_37(string s) => ValidateEquals(s == "222Йж3ж3", s, "222Йж3ж3"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_38(string s) => ValidateEquals(s == "ж303aЙ12", s, "ж303aЙ12"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_39(string s) => ValidateEquals(s == "ьb22322b", s, "ьb22322b"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_40(string s) => ValidateEquals(s == "a22b10b1Й", s, "a22b10b1Й"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_41(string s) => ValidateEquals(s == "3ba2221ь3", s, "3ba2221ь3"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_42(string s) => ValidateEquals(s == "жa1Й0b1Й1", s, "жa1Й0b1Й1"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_43(string s) => ValidateEquals(s == "a20Йжж1ьь", s, "a20Йжж1ьь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_44(string s) => ValidateEquals(s == "ьaж32132ь", s, "ьaж32132ь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_45(string s) => ValidateEquals(s == "11111Й3Й12", s, "11111Й3Й12"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_46(string s) => ValidateEquals(s == "11Й\02Йb3жж", s, "11Й\02Йb3жж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_47(string s) => ValidateEquals(s == "21bжжж0103", s, "21bжжж0103"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_48(string s) => ValidateEquals(s == "333332aЙ11", s, "333332aЙ11"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_49(string s) => ValidateEquals(s == "Й123112313", s, "Й123112313"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_50(string s) => ValidateEquals(s == "12ЙьЙaЙ11ьb", s, "12ЙьЙaЙ11ьb"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_51(string s) => ValidateEquals(s == "жж22221Й3Й2", s, "жж22221Й3Й2"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_52(string s) => ValidateEquals(s == "ьЙ1bbж3202ж", s, "ьЙ1bbж3202ж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_53(string s) => ValidateEquals(s == "1bbЙ2Й33Й2ж", s, "1bbЙ2Й33Й2ж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_54(string s) => ValidateEquals(s == "2013133ь1bж", s, "2013133ь1bж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_55(string s) => ValidateEquals(s == "23a2\02жa2a13", s, "23a2\02жa2a13"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_56(string s) => ValidateEquals(s == "23Й210Й3a3ж1", s, "23Й210Й3a3ж1"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_57(string s) => ValidateEquals(s == "32Й2133bb2Й3", s, "32Й2133bb2Й3"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_58(string s) => ValidateEquals(s == "Й3bb1ь3bbьb3", s, "Й3bb1ь3bbьb3"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_59(string s) => ValidateEquals(s == "a0Йbabж2Й133", s, "a0Йbabж2Й133"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_60(string s) => ValidateEquals(s == "320жa22a11ж1b", s, "320жa22a11ж1b"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_61(string s) => ValidateEquals(s == "ь321b3ьЙЙ13Й2", s, "ь321b3ьЙЙ13Й2"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_62(string s) => ValidateEquals(s == "a3ь1ж2a\022a1a", s, "a3ь1ж2a\022a1a"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_63(string s) => ValidateEquals(s == "3Йb30b33231bь", s, "3Йb30b33231bь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_64(string s) => ValidateEquals(s == "2210121ж13231", s, "2210121ж13231"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_65(string s) => ValidateEquals(s == "013311aa3203Й1", s, "013311aa3203Й1"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_66(string s) => ValidateEquals(s == "12ЙЙ1Й2aЙ2ьbЙa", s, "12ЙЙ1Й2aЙ2ьbЙa"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_67(string s) => ValidateEquals(s == "2b1Й11130221bь", s, "2b1Й11130221bь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_68(string s) => ValidateEquals(s == "230110Й0b3112ж", s, "230110Й0b3112ж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_69(string s) => ValidateEquals(s == "a213ьab121b332", s, "a213ьab121b332"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_70(string s) => ValidateEquals(s == "111a01ж3121b123", s, "111a01ж3121b123"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_71(string s) => ValidateEquals(s == "13a322Й2Й3bжb0Й", s, "13a322Й2Й3bжb0Й"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_72(string s) => ValidateEquals(s == "\021232b1Йaa1032", s, "\021232b1Йaa1032"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_73(string s) => ValidateEquals(s == "жЙ112ьb12Йь3b2ж", s, "жЙ112ьb12Йь3b2ж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_74(string s) => ValidateEquals(s == "2bьь331bb\023122", s, "2bьь331bb\023122"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_75(string s) => ValidateEquals(s == "aж22Й2203b023bь3", s, "aж22Й2203b023bь3"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_76(string s) => ValidateEquals(s == "aЙ033ж3a220ь3331", s, "aЙ033ж3a220ь3331"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_77(string s) => ValidateEquals(s == "20Йжa1b1313жЙb2a", s, "20Йжa1b1313жЙb2a"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_78(string s) => ValidateEquals(s == "131Й1\022ж2322123", s, "131Й1\022ж2322123"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_79(string s) => ValidateEquals(s == "23323b21ь11bЙ321", s, "23323b21ь11bЙ321"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_80(string s) => ValidateEquals(s == "302aьжa3213жaЙ3Йж", s, "302aьжa3213жaЙ3Йж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_81(string s) => ValidateEquals(s == "ж13b00210b1212102", s, "ж13b00210b1212102"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_82(string s) => ValidateEquals(s == "20320Й3Й3ьж3Й2122", s, "20320Й3Й3ьж3Й2122"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_83(string s) => ValidateEquals(s == "0bb23a30baЙb2333ь", s, "0bb23a30baЙb2333ь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_84(string s) => ValidateEquals(s == "22122ь130230103a2", s, "22122ь130230103a2"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_85(string s) => ValidateEquals(s == "ььba20ж1жьЙьbЙ31bж", s, "ььba20ж1жьЙьbЙ31bж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_86(string s) => ValidateEquals(s == "bb1ь1033bж3011bж10", s, "bb1ь1033bж3011bж10"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_87(string s) => ValidateEquals(s == "1ь320a3a22b3333b13", s, "1ь320a3a22b3333b13"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_88(string s) => ValidateEquals(s == "0a22aЙжa2222ж23Й13", s, "0a22aЙжa2222ж23Й13"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_89(string s) => ValidateEquals(s == "Й11Й213212ж1233b23", s, "Й11Й213212ж1233b23"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_90(string s) => ValidateEquals(s == "32ь1Й03123ь011332ab", s, "32ь1Й03123ь011332ab"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_91(string s) => ValidateEquals(s == "222ж2311b133b3ж3223", s, "222ж2311b133b3ж3223"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_92(string s) => ValidateEquals(s == "0111ь3002222a3aaaa3", s, "0111ь3002222a3aaaa3"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_93(string s) => ValidateEquals(s == "313Й213aж01a12231a2", s, "313Й213aж01a12231a2"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_94(string s) => ValidateEquals(s == "1ж022ь1323b3b3ж222ь", s, "1ж022ь1323b3b3ж222ь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_95(string s) => ValidateEquals(s == "ь023a3b213ь033ж13231", s, "ь023a3b213ь033ж13231"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_96(string s) => ValidateEquals(s == "ab2b0bь322300ж2220ж2", s, "ab2b0bь322300ж2220ж2"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_97(string s) => ValidateEquals(s == "1133Й323223ж31002123", s, "1133Й323223ж31002123"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_98(string s) => ValidateEquals(s == "233ж0b3Й023Йьaaж3321", s, "233ж0b3Й023Йьaaж3321"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_99(string s) => ValidateEquals(s == "3Й11b313323230a02Й30", s, "3Й11b313323230a02Й30"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_100(string s) => ValidateEquals(s == "1ж2Йж0131a2ж2aЙЙ3ьb11", s, "1ж2Йж0131a2ж2aЙЙ3ьb11"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_101(string s) => ValidateEquals(s == "Й13303ba3ьж31a1102222", s, "Й13303ba3ьж31a1102222"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_102(string s) => ValidateEquals(s == "32331221ь3ьb103212132", s, "32331221ь3ьb103212132"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_103(string s) => ValidateEquals(s == "133Й0332210231331100Й", s, "133Й0332210231331100Й"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_104(string s) => ValidateEquals(s == "22221322Й1133bb0Й3222", s, "22221322Й1133bb0Й3222"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_105(string s) => ValidateEquals(s == "12b011ж3a1ж3ЙЙa12Й0ь3ь", s, "12b011ж3a1ж3ЙЙa12Й0ь3ь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_106(string s) => ValidateEquals(s == "0333ь12113ь11331ж323Йж", s, "0333ь12113ь11331ж323Йж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_107(string s) => ValidateEquals(s == "0Й13a310ь12\02ь\02320331", s, "0Й13a310ь12\02ь\02320331"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_108(string s) => ValidateEquals(s == "022b2ьж0302b33Й21ж1112", s, "022b2ьж0302b33Й21ж1112"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_109(string s) => ValidateEquals(s == "3322ж2133133b3032Йaa12", s, "3322ж2133133b3032Йaa12"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_110(string s) => ValidateEquals(s == "Й132Йaьb33a3Й33Йb21a2b2", s, "Й132Йaьb33a3Й33Йb21a2b2"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_111(string s) => ValidateEquals(s == "31102113Й11жb31bЙ12b133", s, "31102113Й11жb31bЙ12b133"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_112(string s) => ValidateEquals(s == "ЙьЙЙ0Й03a\023ь3311ьЙ1323", s, "ЙьЙЙ0Й03a\023ь3311ьЙ1323"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_113(string s) => ValidateEquals(s == "212323жa23203bb00жa12ж3", s, "212323жa23203bb00жa12ж3"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_114(string s) => ValidateEquals(s == "жЙ31130ж32322313010aa13", s, "жЙ31130ж32322313010aa13"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_115(string s) => ValidateEquals(s == "123aж2221\022ж22Й021bЙЙ0Й", s, "123aж2221\022ж22Й021bЙЙ0Й"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_116(string s) => ValidateEquals(s == "211131ж2213303b1b0231a11", s, "211131ж2213303b1b0231a11"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_117(string s) => ValidateEquals(s == "ь1aЙжь0110Й2b220жж3ьж3ж1", s, "ь1aЙжь0110Й2b220жж3ьж3ж1"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_118(string s) => ValidateEquals(s == "3жab2221133331311\023ЙЙ3ж", s, "3жab2221133331311\023ЙЙ3ж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_119(string s) => ValidateEquals(s == "21Й20\02ьь333ьb332223Йж1Й", s, "21Й20\02ьь333ьb332223Йж1Й"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_120(string s) => ValidateEquals(s == "1Й2120a01110Й1121003a3b33", s, "1Й2120a01110Й1121003a3b33"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_121(string s) => ValidateEquals(s == "3021a1Й1aa1111b22Й112Й201", s, "3021a1Й1aa1111b22Й112Й201"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_122(string s) => ValidateEquals(s == "2b21ьaьb\023Й33301Й3123Йж1", s, "2b21ьaьb\023Й33301Й3123Йж1"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_123(string s) => ValidateEquals(s == "2ж1baЙЙ1a\021ж23323жbж331ж", s, "2ж1baЙЙ1a\021ж23323жbж331ж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_124(string s) => ValidateEquals(s == "bab12332ж31130Й3230ь1011a", s, "bab12332ж31130Й3230ь1011a"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_125(string s) => ValidateEquals(s == "110aь31ж33ьь33333a2b32ь12ь", s, "110aь31ж33ьь33333a2b32ь12ь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_126(string s) => ValidateEquals(s == "a2жЙ11ь1bь312a11aьaьb02Йb0", s, "a2жЙ11ь1bь312a11aьaьb02Йb0"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_127(string s) => ValidateEquals(s == "32b2ж12a32a3ж23ж1ьЙbb22213", s, "32b2ж12a32a3ж23ж1ьЙbb22213"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_128(string s) => ValidateEquals(s == "30ж111ь11120жжb10212жbь33Й", s, "30ж111ь11120жжb10212жbь33Й"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_129(string s) => ValidateEquals(s == "33b1311ж1\023bж020Й10b0302ж", s, "33b1311ж1\023bж020Й10b0302ж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_130(string s) => ValidateEquals(s == "b3122жa12\021123a3130100113ь", s, "b3122жa12\021123a3130100113ь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_131(string s) => ValidateEquals(s == "302a1ж322\021221\02a1331b2жь1", s, "302a1ж322\021221\02a1331b2жь1"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_132(string s) => ValidateEquals(s == "31201322жж\0221Й\021Йьь32Й11ж", s, "31201322жж\0221Й\021Йьь32Й11ж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_133(string s) => ValidateEquals(s == "3bжa132a13ba1311ж1Й22ЙbЙa33", s, "3bжa132a13ba1311ж1Й22ЙbЙa33"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_134(string s) => ValidateEquals(s == "b33Й113ЙЙab1b332211222Й32\02", s, "b33Й113ЙЙab1b332211222Й32\02"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_135(string s) => ValidateEquals(s == "0ж333b31b212121b1aж02ж133111", s, "0ж333b31b212121b1aж02ж133111"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_136(string s) => ValidateEquals(s == "0101Й220Й0жЙ3Й2abba0b1223aab", s, "0101Й220Й0жЙ3Й2abba0b1223aab"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_137(string s) => ValidateEquals(s == "2Й330bЙ123ж2ж02ж212ь112111Й1", s, "2Й330bЙ123ж2ж02ж212ь112111Й1"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_138(string s) => ValidateEquals(s == "22a\0212aь3b1303Й3bb2b313Й222", s, "22a\0212aь3b1303Й3bb2b313Й222"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_139(string s) => ValidateEquals(s == "2ьж133332102222\020Йжbb2\022\02", s, "2ьж133332102222\020Йжbb2\022\02"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_140(string s) => ValidateEquals(s == "2\02321b31123231b2ЙЙ122abЙ2131", s, "2\02321b31123231b2ЙЙ122abЙ2131"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_141(string s) => ValidateEquals(s == "1b021ьЙ30a2332ь3Й12231жж1aжь1", s, "1b021ьЙ30a2332ь3Й12231жж1aжь1"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_142(string s) => ValidateEquals(s == "1ж3ьь3ь1ь1ь0ж1Й122132a2ььaЙ3b", s, "1ж3ьь3ь1ь1ь0ж1Й122132a2ььaЙ3b"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_143(string s) => ValidateEquals(s == "21bbb31301ь3жaaж0Й3323b33ь1ь1", s, "21bbb31301ь3жaaж0Й3323b33ь1ь1"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_144(string s) => ValidateEquals(s == "a00Йь11жжaa321ьЙ1Й31жa21ж3223", s, "a00Йь11жжaa321ьЙ1Й31жa21ж3223"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_145(string s) => ValidateEquals(s == "3132b0Йb3110ab\0201Й1ж32222a33ж", s, "3132b0Йb3110ab\0201Й1ж32222a33ж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_146(string s) => ValidateEquals(s == "32b110bb312ь02Й1b2Й23232Й12ь33", s, "32b110bb312ь02Й1b2Й23232Й12ь33"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_147(string s) => ValidateEquals(s == "ж121bbbЙ2b1ж12222Йь1Йb02013жь1", s, "ж121bbbЙ2b1ж12222Йь1Йb02013жь1"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_148(string s) => ValidateEquals(s == "ь1b00a3310231001b1a1ь33жжb130ь", s, "ь1b00a3310231001b1a1ь33жжb130ь"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_149(string s) => ValidateEquals(s == "ж3b211b121ж23bь12a1Й2Йж12313aж", s, "ж3b211b121ж23bь12a1Й2Йж12313aж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_150(string s) => ValidateEquals(s == "1a3жb31311322жь33213Й3ь13330жa3", s, "1a3жb31311322жь33213Й3ь13330жa3"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_151(string s) => ValidateEquals(s == "b33Йbж3333233101a33ж3b231221ь11", s, "b33Йbж3333233101a33ж3b231221ь11"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_152(string s) => ValidateEquals(s == "1Й212Й3ж112a31aьжьЙ32ж233a32Й1ж", s, "1Й212Й3ж112a31aьжьЙ32ж233a32Й1ж"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_153(string s) => ValidateEquals(s == "133ь02aьa0Й3Йab3ь1Й3Й2a21121210", s, "133ь02aьa0Й3Йab3ь1Й3Й2a21121210"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_154(string s) => ValidateEquals(s == "1320baж31b3Й2Й1322b113212212331", s, "1320baж31b3Й2Й1322b113212212331"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_155(string s) => ValidateEquals(s == "1Йa332132жb31Й33Й32321ж31b120ж03", s, "1Йa332132жb31Й33Й32321ж31b120ж03"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_156(string s) => ValidateEquals(s == "213321a1b3жь3111жЙ2b2Й3101221ь33", s, "213321a1b3жь3111жЙ2b2Й3101221ь33"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_157(string s) => ValidateEquals(s == "2ж1311a23b2212aЙ21Й11жb3233bb3a1", s, "2ж1311a23b2212aЙ21Й11жb3233bb3a1"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_158(string s) => ValidateEquals(s == "01Й11113ь3Й32a3ьЙЙ3Й32b2ab221310", s, "01Й11113ь3Й32a3ьЙЙ3Й32b2ab221310"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void Equals_159(string s) => ValidateEquals(s == "a120213b11211\0223223312ьь1Й3222Й", s, "a120213b11211\0223223312ьь1Й3222Й"); + + public static readonly string[] s_TestData = + { + null, + "", + "\0", + "a", + "0", + "ж", + "3", + "33", + "31", + "a\0", + "12", + "13", + "b12", + "ж23", + "2a2", + "222", + "0ь3", + "bж31", + "ьЙbЙ", + "b033", + "311ь", + "жЙ12", + "2011b", + "222b2", + "aЙ213", + "1a131", + "3232Й", + "3b0ьжь", + "213b2Й", + "b31210", + "1ж0021", + "3ь3112", + "122b231", + "03ж32ж3", + "bb31ж2Й", + "023bьжЙ", + "\0232a12", + "ж13ь11Йь", + "11ьbb32ь", + "222Йж3ж3", + "ж303aЙ12", + "ьb22322b", + "a22b10b1Й", + "3ba2221ь3", + "жa1Й0b1Й1", + "a20Йжж1ьь", + "ьaж32132ь", + "11111Й3Й12", + "11Й\02Йb3жж", + "21bжжж0103", + "333332aЙ11", + "Й123112313", + "12ЙьЙaЙ11ьb", + "жж22221Й3Й2", + "ьЙ1bbж3202ж", + "1bbЙ2Й33Й2ж", + "2013133ь1bж", + "23a2\02жa2a13", + "23Й210Й3a3ж1", + "32Й2133bb2Й3", + "Й3bb1ь3bbьb3", + "a0Йbabж2Й133", + "320жa22a11ж1b", + "ь321b3ьЙЙ13Й2", + "a3ь1ж2a\022a1a", + "3Йb30b33231bь", + "2210121ж13231", + "013311aa3203Й1", + "12ЙЙ1Й2aЙ2ьbЙa", + "2b1Й11130221bь", + "230110Й0b3112ж", + "a213ьab121b332", + "111a01ж3121b123", + "13a322Й2Й3bжb0Й", + "\021232b1Йaa1032", + "жЙ112ьb12Йь3b2ж", + "2bьь331bb\023122", + "aж22Й2203b023bь3", + "aЙ033ж3a220ь3331", + "20Йжa1b1313жЙb2a", + "131Й1\022ж2322123", + "23323b21ь11bЙ321", + "302aьжa3213жaЙ3Йж", + "ж13b00210b1212102", + "20320Й3Й3ьж3Й2122", + "0bb23a30baЙb2333ь", + "22122ь130230103a2", + "ььba20ж1жьЙьbЙ31bж", + "bb1ь1033bж3011bж10", + "1ь320a3a22b3333b13", + "0a22aЙжa2222ж23Й13", + "Й11Й213212ж1233b23", + "32ь1Й03123ь011332ab", + "222ж2311b133b3ж3223", + "0111ь3002222a3aaaa3", + "313Й213aж01a12231a2", + "1ж022ь1323b3b3ж222ь", + "ь023a3b213ь033ж13231", + "ab2b0bь322300ж2220ж2", + "1133Й323223ж31002123", + "233ж0b3Й023Йьaaж3321", + "3Й11b313323230a02Й30", + "1ж2Йж0131a2ж2aЙЙ3ьb11", + "Й13303ba3ьж31a1102222", + "32331221ь3ьb103212132", + "133Й0332210231331100Й", + "22221322Й1133bb0Й3222", + "12b011ж3a1ж3ЙЙa12Й0ь3ь", + "0333ь12113ь11331ж323Йж", + "0Й13a310ь12\02ь\02\02320331", + "022b2ьж0302b33Й21ж1112", + "3322ж2133133b3032Йaa12", + "Й132Йaьb33a3Й33Йb21a2b2", + "31102113Й11жb31bЙ12b133", + "ЙьЙЙ0Й03a\023ь3311ьЙ1323", + "212323жa23203bb00жa12ж3", + "жЙ31130ж32322313010aa13", + "123aж2221\022ж22Й021bЙЙ0Й", + "211131ж2213303b1b0231a11", + "ь1aЙжь0110Й2b220жж3ьж3ж1", + "3жab2221133331311\023ЙЙ3ж", + "21Й20\02ьь333ьb332223Йж1Й", + "1Й2120a01110Й1121003a3b33", + "3021a1Й1aa1111b22Й112Й201", + "2b21ьaьb\023Й33301Й3123Йж1", + "2ж1baЙЙ1a\021ж23323жbж331ж", + "bab12332ж31130Й3230ь1011a", + "110aь31ж33ьь33333a2b32ь12ь", + "a2жЙ11ь1bь312a11aьaьb02Йb0", + "32b2ж12a32a3ж23ж1ьЙbb22213", + "30ж111ь11120жжb10212жbь33Й", + "33b1311ж1\023bж020Й10b0302ж", + "b3122жa12\021123a3130100113ь", + "302a1ж322\021221z2a1331b2жь1", + "31201322жж\0221Йz21Йьь32Й11ж", + "3bжa132a13ba1311ж1Й22ЙbЙa33", + "b33Й113ЙЙab1b332211222Й32\02", + "0ж333b31b212121b1aж02ж133111", + "0101Й220Й0жЙ3Й2abba0b1223aa\0", + "2Й330bЙ123ж2ж02ж212ь112111Й1", + "22a\0212aь3b1303Й3bb2b313Й222", + "2ьж133332102222\020Йжbb2\022\02", + "2\02321b31123231b2ЙЙ122abЙ2131", + "1b021ьЙ30a2332ь3Й12231жж1aжь1", + "1ж3ьь3ь1ь1ь0ж1Й122132a2ььaЙ3b", + "21bbb31301ь3жaaж0Й3323b33ь1ь1", + "a00Йь11жжaa321ьЙ1Й31жa21ж3223", + "3132b0Йb3110ab\0201Й1ж32222a33ж", + "32b110bb312ь02Й1b2Й23232Й12ь33", + "ж121bbbЙ2b1ж12222Йь1Йb02013жь1", + "ь1b00a3310231001b1a1ь33жжb130ь", + "ж3b211b121ж23bь12a1Й2Йж12313aж", + "1a3жb31311322жь33213Й3ь13330жa3", + "b33Йbж3333233101a33ж3b231221ь11", + "1Й212Й3ж112a31aьжьЙ32ж233a32Й1ж", + "133ь02aьa0Й3Йab3ь1Й3Й2a21121210", + "1320baж31b3Й2Й1322b113212212331", + "1Йa332132жb31Й33Й32321ж31b120ж03", + "213321a1b3жь3111жЙ2b2Й3101221ь33", + "2ж1311a23b2212aЙ21Й11жb3233bb3a1", + "01Й11113ь3Й32a3ьЙЙ3Й32b2ab221310", + "a120213b11211\0223223312ьь1Й3222Й", + }; +} \ No newline at end of file diff --git a/src/tests/JIT/opt/AutoVectorization/StringEqualsAutoVectorization.csproj b/src/tests/JIT/opt/AutoVectorization/StringEqualsAutoVectorization.csproj new file mode 100644 index 00000000000000..6946bed81bfd5b --- /dev/null +++ b/src/tests/JIT/opt/AutoVectorization/StringEqualsAutoVectorization.csproj @@ -0,0 +1,9 @@ + + + Exe + True + + + + + From a27a975f34228c6c1ed9a1e2f5bbc25d85814e6f Mon Sep 17 00:00:00 2001 From: EgorBo Date: Thu, 10 Feb 2022 05:32:44 +0300 Subject: [PATCH 19/19] do 8-char longs strings via SIMD path - it's smaller --- .../src/System/String.Comparison.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index fab6039b3c3f14..4f08d3b3e1887a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -689,9 +689,9 @@ public static bool Equals(string? a, string? b) if (RuntimeHelpers.IsKnownConstant(b) && !RuntimeHelpers.IsKnownConstant(a) && b != null) { // Unroll using SWAR - if (b.Length <= 8) + if (b.Length <= 7) { - return EqualsUnrolled_0_to_8(a, b); + return EqualsUnrolled_0_to_7(a, b); } // Unroll using two Vector128s else if (b.Length <= 16 && Vector128.IsHardwareAccelerated) @@ -704,7 +704,7 @@ public static bool Equals(string? a, string? b) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool EqualsUnrolled_0_to_8(string? a, string b) + static bool EqualsUnrolled_0_to_7(string? a, string b) { if (a != null) { @@ -746,7 +746,7 @@ static bool EqualsUnrolled_0_to_8(string? a, string b) return a.Length == 4 && v1 == cns1; } - // Handle Length [5..8] via two ulong (overlapped) + // Handle Length [5..7] via two ulong (overlapped) return a.Length == b.Length && v1 == cns1 && ReadUInt64(a, b.Length - 4) == (((ulong)b[b.Length - 1] << 48) | ((ulong)b[b.Length - 2] << 32) |