diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 1cdebfb9c3c8aa..62ce94464cc9fe 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3888,6 +3888,8 @@ class Compiler void impImportLeave(BasicBlock* block); void impResetLeaveBlock(BasicBlock* block, unsigned jmpAddr); GenTree* impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom); + GenTree* impUnrollSpanComparisonWithStrCon(GenTree* span, GenTreeStrCon* cnsStr, bool ignoreCase, bool startsWith); + GenTree* impIntrinsic(GenTree* newobjThis, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index d7576d6b4d6085..d3b17d9e084a6b 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -576,6 +576,22 @@ inline unsigned genTypeSize(T type) return genTypeSizes[TypeGet(type)]; } +//------------------------------------------------------------------------------ +// genTrimUnsignedValue: Trim size_t value to the size of the given type. +// +// Arguments: +// type - the type to fit the value into. +// value - the value. +// +// Return Value: +// The value trimmed to the size of the given type. + +inline size_t genTrimUnsignedValue(var_types type, size_t value) +{ + assert(genTypeSize(type) <= sizeof(size_t)); + return (((size_t)-1) >> ((sizeof(size_t) - genTypeSize(type)) * 8)) & value; +} + /***************************************************************************** * * Return the "stack slot count" of the given type. diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index aa75207070bd37..c7ec417fa6f237 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4318,6 +4318,97 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } + case NI_System_MemoryExtensions_Equals: + case NI_System_MemoryExtensions_SequenceEqual: + case NI_System_MemoryExtensions_StartsWith: + { + assert((sig->numArgs == 2) || (sig->numArgs == 3)); + + // We're looking for: + // + // bool x1 = arg0.StartsWith(String.op_Implicit("cstr")); + // bool x2 = arg0.SequenceEqual(arg0, String.op_Implicit("cstr")); + // + // In order to optimize it into: + // + // bool x1 = arg0.Length >= 4 && *(arg0._pointer) == ToHexConst("cstr"); + // bool x2 = arg0.Length == 4 && *(arg0._pointer) == ToHexConst("cstr"); + // + GenTree* arg0 = impStackTop(sig->numArgs - 1).val; + GenTree* arg1 = impStackTop(sig->numArgs - 2).val; + bool ignoreCase = false; + + if ((sig->numArgs == 3) && impStackTop(0).val->IsCnsIntOrI()) + { + assert((ni == NI_System_MemoryExtensions_Equals) || (ni == NI_System_MemoryExtensions_StartsWith)); + + // See StringComparison.cs + const int Ordinal = 4; + const int OrdinalIgnoreCase = 5; + + // Fetch mode from the last argument. + int mode = (int)impStackTop(0).val->AsIntCon()->IconValue(); + + if (mode == OrdinalIgnoreCase) + { + ignoreCase = true; + } + else if (mode == Ordinal) + { + ignoreCase = false; + } + else + { + // This mode is not supported. + assert(mode <= OrdinalIgnoreCase); + return nullptr; + } + } + else if (sig->numArgs == 3) + { + // Comparison mode is not a constant. + return nullptr; + } + + if (arg1->OperIs(GT_RET_EXPR)) + { + GenTreeCall* strToSpanCall = arg1->AsRetExpr()->gtInlineCandidate->AsCall(); + if (!(strToSpanCall->gtFlags & CORINFO_FLG_JIT_INTRINSIC)) + { + // strToSpanCall must be either: + // ReadOnlySpan String.op_Implicit(String) or + // ReadOnlySpan MemoryExtensions.AsSpan(String) + break; + } + + NamedIntrinsic strToSpanNi = lookupNamedIntrinsic(strToSpanCall->gtCallMethHnd); + if ((strToSpanNi != NI_System_String_op_Implicit) && + (strToSpanNi != NI_System_MemoryExtensions_AsSpan)) + { + break; + } + if (!strToSpanCall->gtCallArgs->GetNode()->OperIs(GT_CNS_STR)) + { + break; + } + + GenTreeStrCon* strCon = strToSpanCall->gtCallArgs->GetNode()->AsStrCon(); + bool startsWith = (ni == NI_System_MemoryExtensions_StartsWith); + GenTree* newNode = impUnrollSpanComparisonWithStrCon(arg0, strCon, ignoreCase, startsWith); + if (newNode != nullptr) + { + retNode = newNode; + strToSpanCall->ReplaceWith(gtNewNothingNode(), this); + for (unsigned i = 0; i < sig->numArgs; i++) + { + impPopStack(); + } + break; + } + } + break; + } + case NI_System_Threading_Thread_get_ManagedThreadId: { if (opts.OptimizationEnabled() && impStackTop().val->OperIs(GT_RET_EXPR)) @@ -4573,6 +4664,154 @@ GenTree* Compiler::impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom) return nullptr; } +GenTree* Compiler::impUnrollSpanComparisonWithStrCon(GenTree* span, + GenTreeStrCon* cnsStr, + bool ignoreCase, + bool startsWith) +{ +#ifdef TARGET_64BIT + // Grab the actual string literal from VM + int strLen = -1; + LPCWSTR str = info.compCompHnd->getStringLiteral(cnsStr->gtScpHnd, cnsStr->gtSconCPX, &strLen); + var_types cmpType; + + if (strLen == 0) + { + if (startsWith) + { + // Any span starts with "", return true + return gtNewIconNode(1); + } + else + { + // TODO: Emit `span.Length == 0` + return nullptr; + } + } + else if (strLen == 1) + { + cmpType = TYP_SHORT; + } + else if (strLen == 2) + { + cmpType = TYP_INT; + } + else if (strLen == 4) + { + cmpType = TYP_LONG; + } + else + { + // TODO: Support other sizes and emit SIMD for larger strings + return nullptr; + } + + if (str == nullptr) + { + // getStringLiteral couldn't manage to get the literal (e.g. in dynamic context) + return nullptr; + } + + bool allAreLetters = true; + UINT64 strAsUlong = 0; + + for (int i = 0; i < strLen; i++) + { + UINT64 strChar = str[i]; + if (strChar > 127) + { + // str is not ASCII - bail out. + return nullptr; + } + + bool isLetter = ((strChar >= 'A') && (strChar <= 'Z')) || ((strChar >= 'a') && (strChar <= 'z')); + if (!isLetter) + { + allAreLetters = false; + } + strAsUlong |= (strChar << 16UL * i); + } + + if (ignoreCase) + { + if (!allAreLetters) + { + // TODO: Implement logic from UInt64OrdinalIgnoreCaseAscii + // + // bool x = (((IND ^ strAsUlong)<<2) & + // (((strAsUlong+0x0005000500050005ul)|0x00A000A000A000A0ul)+0x001A001A001A001Aul)|0xFF7FFF7FFF7FFF7Ful)==0; + // + return nullptr; + } + strAsUlong |= 0x0020002000200020ULL; + } + + // We're going to emit the following tree: + // + // \--* QMARK int + // +--* GE/EQ int + // | +--* FIELD int Span._length + // | \--* CNS_INT int %strLen% + // \--* COLON int + // +--* CNS_INT int 0 (false) + // \--* EQ int + // +--* IND long + // | \--* FIELD byref Span._pointer + // \--* CNS_INT long %strAsUlong% + // + + CORINFO_CLASS_HANDLE spanCls = gtGetStructHandle(span); + CORINFO_FIELD_HANDLE pointerHnd = info.compCompHnd->getFieldInClass(spanCls, 0); + CORINFO_FIELD_HANDLE lengthHnd = info.compCompHnd->getFieldInClass(spanCls, 1); + const unsigned pointerOffset = info.compCompHnd->getFieldOffset(pointerHnd); + const unsigned lengthOffset = info.compCompHnd->getFieldOffset(lengthHnd); + + GenTree* spanRef = span; + if (span->TypeIs(TYP_STRUCT)) + { + spanRef = gtNewOperNode(GT_ADDR, TYP_BYREF, span); + } + assert(spanRef->TypeIs(TYP_BYREF)); + + // We're going to use spanRef twice so need to clone it + GenTree* spanRefClone = nullptr; + spanRef = + impCloneExpr(spanRef, &spanRefClone, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, nullptr DEBUGARG("spanRef")); + + GenTree* spanData = gtNewFieldRef(TYP_BYREF, pointerHnd, spanRefClone, pointerOffset); + GenTree* spanDataIndir = gtNewIndir(cmpType, spanData); + GenTreeIntCon* constStrAsIntCon = gtNewIconNode(genTrimUnsignedValue(cmpType, strAsUlong), cmpType); + + if (ignoreCase) + { + ssize_t lowerBitMask = genTrimUnsignedValue(cmpType, 0x0020002000200020ULL); + + // Set "is lower" bits in all chars + spanDataIndir = gtNewOperNode(GT_OR, cmpType, spanDataIndir, gtNewIconNode(lowerBitMask, cmpType)); + } + + // TODO: for length == 3 (not supported yet) we need to do two indir cmp ops: + // + // bool x = (*(Int64*)span._pointer ^ 0xXXX) | (*((Int32*)span._pointer + 2) ^ 0xYYY) != 0 + // + GenTree* indirCmp = gtNewOperNode(GT_EQ, TYP_INT, spanDataIndir, constStrAsIntCon); + GenTree* spanLenField = gtNewFieldRef(TYP_INT, lengthHnd, spanRef, lengthOffset); + GenTreeColon* colon = new (this, GT_COLON) GenTreeColon(TYP_INT, indirCmp, gtNewIconNode(0)); + + GenTreeQmark* qmark = + gtNewQmarkNode(TYP_INT, gtNewOperNode(startsWith ? GT_GE : GT_EQ, TYP_INT, spanLenField, gtNewIconNode(strLen)), + colon); + + // Spill qmark into a temp. + unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling STARTSWITH root qmark")); + impAssignTempGen(tmp, qmark, (unsigned)CHECK_SPILL_NONE); + return gtNewLclvNode(tmp, TYP_INT); +#else // TARGET_64BIT + // TODO: Enable for 32 at least for length [0..2] + return nullptr; +#endif +} + GenTree* Compiler::impMathIntrinsic(CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, var_types callType, @@ -4831,6 +5070,32 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) result = NI_System_Type_IsAssignableTo; } } + else if (strcmp(className, "MemoryExtensions") == 0) + { + if (strcmp(methodName, "StartsWith") == 0) + { + result = NI_System_MemoryExtensions_StartsWith; + } + else if (strcmp(methodName, "SequenceEqual") == 0) + { + result = NI_System_MemoryExtensions_SequenceEqual; + } + else if (strcmp(methodName, "Equals") == 0) + { + result = NI_System_MemoryExtensions_Equals; + } + else if (strcmp(methodName, "AsSpan") == 0) + { + result = NI_System_MemoryExtensions_AsSpan; + } + } + else if (strcmp(className, "String") == 0) + { + if (strcmp(methodName, "op_Implicit") == 0) + { + result = NI_System_String_op_Implicit; + } + } } else if (strcmp(namespaceName, "System.Threading") == 0) { diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index c223307eebe9fe..ed5cd19c60795c 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -44,6 +44,11 @@ enum NamedIntrinsic : unsigned short NI_System_Type_IsAssignableFrom, NI_System_Type_IsAssignableTo, NI_System_Array_Clone, + NI_System_MemoryExtensions_AsSpan, + NI_System_MemoryExtensions_Equals, + NI_System_MemoryExtensions_StartsWith, + NI_System_MemoryExtensions_SequenceEqual, + NI_System_String_op_Implicit, // These are used by HWIntrinsics but are defined more generally // to allow dead code optimization and handle the recursion case diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs index ce136377a41608..3876637a3e9667 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs @@ -43,6 +43,7 @@ public static bool Contains(this ReadOnlySpan span, ReadOnlySpan val /// The value to compare with the source span. /// One of the enumeration values that determines how the and are compared. /// + [Intrinsic] public static bool Equals(this ReadOnlySpan span, ReadOnlySpan other, StringComparison comparisonType) { string.CheckStringComparison(comparisonType); @@ -331,6 +332,7 @@ ref MemoryMarshal.GetReference(value), /// The source span. /// The sequence to compare to the beginning of the source span. /// One of the enumeration values that determines how the and are compared. + [Intrinsic] public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) { string.CheckStringComparison(comparisonType); diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 86038795cef8d7..a6168d4009ea57 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -87,6 +87,7 @@ public static Span AsSpan(this T[]? array, Range range) /// /// The target string. /// Returns default when is null. + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ReadOnlySpan AsSpan(this string? text) { @@ -991,6 +992,7 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(values)), /// /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T). /// + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool SequenceEqual(this ReadOnlySpan span, ReadOnlySpan other) where T : IEquatable { @@ -1038,6 +1040,7 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(other)), /// /// Determines whether the specified sequence appears at the start of the span. /// + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool StartsWith(this Span span, ReadOnlySpan value) where T : IEquatable { @@ -1058,6 +1061,7 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(value)), /// /// Determines whether the specified sequence appears at the start of the span. /// + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan value) where T : IEquatable { diff --git a/src/libraries/System.Private.CoreLib/src/System/String.cs b/src/libraries/System.Private.CoreLib/src/System/String.cs index 41df6d8b79fcd5..92c417ca3a4ff8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.cs @@ -377,6 +377,7 @@ public static string Create(int length, TState state, SpanAction(string? value) => value != null ? new ReadOnlySpan(ref value.GetRawStringData(), value.Length) : default; diff --git a/src/tests/JIT/opt/Unroll/Span_SequenceEqual_ConstString.cs b/src/tests/JIT/opt/Unroll/Span_SequenceEqual_ConstString.cs new file mode 100644 index 00000000000000..42893a674c8ffd --- /dev/null +++ b/src/tests/JIT/opt/Unroll/Span_SequenceEqual_ConstString.cs @@ -0,0 +1,301 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +public static class Program +{ + private static int s_ReturnCode = 100; + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup1(ReadOnlySpan span) + { + AssertEqual(span.SequenceEqual("".ToVar()), span.SequenceEqual("")); + AssertEqual(span.SequenceEqual("a".ToVar()), span.SequenceEqual("a")); + AssertEqual(span.SequenceEqual("z".ToVar()), span.SequenceEqual("z")); + AssertEqual(span.SequenceEqual("A".ToVar()), span.SequenceEqual("A")); + AssertEqual(span.SequenceEqual("Z".ToVar()), span.SequenceEqual("Z")); + AssertEqual(span.SequenceEqual("x".ToVar()), span.SequenceEqual("x")); + AssertEqual(span.SequenceEqual("X".ToVar()), span.SequenceEqual("X")); + AssertEqual(span.SequenceEqual("\r".ToVar()), span.SequenceEqual("\r")); + AssertEqual(span.SequenceEqual("-".ToVar()), span.SequenceEqual("-")); + AssertEqual(span.SequenceEqual("\0".ToVar()), span.SequenceEqual("\0")); + AssertEqual(span.SequenceEqual("ж".ToVar()), span.SequenceEqual("ж")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup2(ReadOnlySpan span) + { + AssertEqual(span.Equals("".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.Equals("", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.Equals("a".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("a", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("z".ToVar(), StringComparison.Ordinal), span.Equals("z", StringComparison.Ordinal)); + AssertEqual(span.Equals("A".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("A", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("Z".ToVar(), StringComparison.InvariantCulture), span.Equals("Z", StringComparison.InvariantCulture)); + AssertEqual(span.Equals("x".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("x", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("X".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("X", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("\r".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.Equals("\r", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.Equals("-".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("-", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("\0".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("\0", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("ж".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("ж", StringComparison.OrdinalIgnoreCase)); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup3(ReadOnlySpan span) + { + AssertEqual(span.SequenceEqual("aa".ToVar()), span.SequenceEqual("aa")); + AssertEqual(span.SequenceEqual("zz".ToVar()), span.SequenceEqual("zz")); + AssertEqual(span.SequenceEqual("AA".ToVar()), span.SequenceEqual("AA")); + AssertEqual(span.SequenceEqual("ZZ".ToVar()), span.SequenceEqual("ZZ")); + AssertEqual(span.SequenceEqual("xx".ToVar()), span.SequenceEqual("xx")); + AssertEqual(span.SequenceEqual("XX".ToVar()), span.SequenceEqual("XX")); + AssertEqual(span.SequenceEqual("\r\r".ToVar()), span.SequenceEqual("\r\r")); + AssertEqual(span.SequenceEqual("--".ToVar()), span.SequenceEqual("--")); + AssertEqual(span.SequenceEqual("\0\0".ToVar()), span.SequenceEqual("\0\0")); + AssertEqual(span.SequenceEqual("жж".ToVar()), span.SequenceEqual("жж")); + AssertEqual(span.SequenceEqual("va".ToVar()), span.SequenceEqual("va")); + AssertEqual(span.SequenceEqual("vz".ToVar()), span.SequenceEqual("vz")); + AssertEqual(span.SequenceEqual("vA".ToVar()), span.SequenceEqual("vA")); + AssertEqual(span.SequenceEqual("vZ".ToVar()), span.SequenceEqual("vZ")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup4(ReadOnlySpan span) + { + AssertEqual(span.Equals("aa".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.Equals("aa", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.Equals("zz".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("zz", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("AA".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("AA", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("ZZ".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("ZZ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("xx".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.Equals("xx", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.Equals("XX".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("XX", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("\r\r".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("\r\r", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("--".ToVar(), StringComparison.Ordinal), span.Equals("--", StringComparison.Ordinal)); + AssertEqual(span.Equals("\0\0".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("\0\0", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("жж".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("жж", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("va".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("va", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("vz".ToVar(), StringComparison.InvariantCulture), span.Equals("vz", StringComparison.InvariantCulture)); + AssertEqual(span.Equals("vA".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("vA", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("vZ".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("vZ", StringComparison.OrdinalIgnoreCase)); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup5(ReadOnlySpan span) + { + AssertEqual(span.SequenceEqual("vx".ToVar()), span.SequenceEqual("vx")); + AssertEqual(span.SequenceEqual("vX".ToVar()), span.SequenceEqual("vX")); + AssertEqual(span.SequenceEqual("v\r".ToVar()), span.SequenceEqual("v\r")); + AssertEqual(span.SequenceEqual("v-".ToVar()), span.SequenceEqual("v-")); + AssertEqual(span.SequenceEqual("v\0".ToVar()), span.SequenceEqual("v\0")); + AssertEqual(span.SequenceEqual("vж".ToVar()), span.SequenceEqual("vж")); + AssertEqual(span.SequenceEqual("aJ".ToVar()), span.SequenceEqual("aJ")); + AssertEqual(span.SequenceEqual("zJ".ToVar()), span.SequenceEqual("zJ")); + AssertEqual(span.SequenceEqual("AJ".ToVar()), span.SequenceEqual("AJ")); + AssertEqual(span.SequenceEqual("ZJ".ToVar()), span.SequenceEqual("ZJ")); + AssertEqual(span.SequenceEqual("xJ".ToVar()), span.SequenceEqual("xJ")); + AssertEqual(span.SequenceEqual("XJ".ToVar()), span.SequenceEqual("XJ")); + AssertEqual(span.SequenceEqual("\rJ".ToVar()), span.SequenceEqual("\rJ")); + AssertEqual(span.SequenceEqual("-J".ToVar()), span.SequenceEqual("-J")); + AssertEqual(span.SequenceEqual("\0J".ToVar()), span.SequenceEqual("\0J")); + AssertEqual(span.SequenceEqual("жJ".ToVar()), span.SequenceEqual("жJ")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup6(ReadOnlySpan span) + { + AssertEqual(span.Equals("vx".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("vx", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("vX".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("vX", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("v\r".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("v\r", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("v-".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("v-", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("v\0".ToVar(), StringComparison.Ordinal), span.Equals("v\0", StringComparison.Ordinal)); + AssertEqual(span.Equals("vж".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("vж", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("aJ".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("aJ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("zJ".ToVar(), StringComparison.InvariantCulture), span.Equals("zJ", StringComparison.InvariantCulture)); + AssertEqual(span.Equals("AJ".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("AJ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("ZJ".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.Equals("ZJ", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.Equals("xJ".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("xJ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("XJ".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.Equals("XJ", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.Equals("\rJ".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("\rJ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("-J".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("-J", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("\0J".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("\0J", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("жJ".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("жJ", StringComparison.OrdinalIgnoreCase)); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup7(ReadOnlySpan span) + { + AssertEqual(span.SequenceEqual("aaa".ToVar()), span.SequenceEqual("aaa")); + AssertEqual(span.SequenceEqual("zzz".ToVar()), span.SequenceEqual("zzz")); + AssertEqual(span.SequenceEqual("AAA".ToVar()), span.SequenceEqual("AAA")); + AssertEqual(span.SequenceEqual("ZZZ".ToVar()), span.SequenceEqual("ZZZ")); + AssertEqual(span.SequenceEqual("xxx".ToVar()), span.SequenceEqual("xxx")); + AssertEqual(span.SequenceEqual("XXX".ToVar()), span.SequenceEqual("XXX")); + AssertEqual(span.SequenceEqual("\r\r\r".ToVar()), span.SequenceEqual("\r\r\r")); + AssertEqual(span.SequenceEqual("---".ToVar()), span.SequenceEqual("---")); + AssertEqual(span.SequenceEqual("\0\0\0".ToVar()), span.SequenceEqual("\0\0\0")); + AssertEqual(span.SequenceEqual("жжж".ToVar()), span.SequenceEqual("жжж")); + AssertEqual(span.SequenceEqual("ava".ToVar()), span.SequenceEqual("ava")); + AssertEqual(span.SequenceEqual("zvz".ToVar()), span.SequenceEqual("zvz")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup8(ReadOnlySpan span) + { + AssertEqual(span.Equals("aaa".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("aaa", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("zzz".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("zzz", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("AAA".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.Equals("AAA", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.Equals("ZZZ".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("ZZZ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("xxx".ToVar(), StringComparison.InvariantCulture), span.Equals("xxx", StringComparison.InvariantCulture)); + AssertEqual(span.Equals("XXX".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.Equals("XXX", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.Equals("\r\r\r".ToVar(), StringComparison.Ordinal), span.Equals("\r\r\r", StringComparison.Ordinal)); + AssertEqual(span.Equals("---".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("---", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("\0\0\0".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("\0\0\0", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("жжж".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("жжж", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("ava".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("ava", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("zvz".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("zvz", StringComparison.OrdinalIgnoreCase)); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup9(ReadOnlySpan span) + { + AssertEqual(span.SequenceEqual("AvA".ToVar()), span.SequenceEqual("AvA")); + AssertEqual(span.SequenceEqual("ZvZ".ToVar()), span.SequenceEqual("ZvZ")); + AssertEqual(span.SequenceEqual("xvx".ToVar()), span.SequenceEqual("xvx")); + AssertEqual(span.SequenceEqual("XvX".ToVar()), span.SequenceEqual("XvX")); + AssertEqual(span.SequenceEqual("\rv\r".ToVar()), span.SequenceEqual("\rv\r")); + AssertEqual(span.SequenceEqual("-v-".ToVar()), span.SequenceEqual("-v-")); + AssertEqual(span.SequenceEqual("\0v\0".ToVar()), span.SequenceEqual("\0v\0")); + AssertEqual(span.SequenceEqual("жvж".ToVar()), span.SequenceEqual("жvж")); + AssertEqual(span.SequenceEqual("aaж".ToVar()), span.SequenceEqual("aaж")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup10(ReadOnlySpan span) + { + AssertEqual(span.Equals("AvA".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("AvA", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("ZvZ".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("ZvZ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("xvx".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.Equals("xvx", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.Equals("XvX".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.Equals("XvX", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.Equals("\rv\r".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("\rv\r", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("-v-".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("-v-", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("\0v\0".ToVar(), StringComparison.InvariantCulture), span.Equals("\0v\0", StringComparison.InvariantCulture)); + AssertEqual(span.Equals("жvж".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("жvж", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("aaж".ToVar(), StringComparison.Ordinal), span.Equals("aaж", StringComparison.Ordinal)); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup11(ReadOnlySpan span) + { + AssertEqual(span.SequenceEqual("aaaa".ToVar()), span.SequenceEqual("aaaa")); + AssertEqual(span.SequenceEqual("zzzz".ToVar()), span.SequenceEqual("zzzz")); + AssertEqual(span.SequenceEqual("AAAA".ToVar()), span.SequenceEqual("AAAA")); + AssertEqual(span.SequenceEqual("ZZZZ".ToVar()), span.SequenceEqual("ZZZZ")); + AssertEqual(span.SequenceEqual("xxxx".ToVar()), span.SequenceEqual("xxxx")); + AssertEqual(span.SequenceEqual("XXXX".ToVar()), span.SequenceEqual("XXXX")); + AssertEqual(span.SequenceEqual("\r\r\r\r".ToVar()), span.SequenceEqual("\r\r\r\r")); + AssertEqual(span.SequenceEqual("----".ToVar()), span.SequenceEqual("----")); + AssertEqual(span.SequenceEqual("\0\0\0\0".ToVar()), span.SequenceEqual("\0\0\0\0")); + AssertEqual(span.SequenceEqual("жжжж".ToVar()), span.SequenceEqual("жжжж")); + AssertEqual(span.SequenceEqual("aaaa".ToVar()), span.SequenceEqual("aaaa")); + AssertEqual(span.SequenceEqual("zzzz".ToVar()), span.SequenceEqual("zzzz")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup12(ReadOnlySpan span) + { + AssertEqual(span.Equals("aaaa".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("aaaa", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("zzzz".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.Equals("zzzz", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.Equals("AAAA".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("AAAA", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("ZZZZ".ToVar(), StringComparison.Ordinal), span.Equals("ZZZZ", StringComparison.Ordinal)); + AssertEqual(span.Equals("xxxx".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("xxxx", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("XXXX".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("XXXX", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("\r\r\r\r".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("\r\r\r\r", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("----".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("----", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("\0\0\0\0".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("\0\0\0\0", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("жжжж".ToVar(), StringComparison.InvariantCulture), span.Equals("жжжж", StringComparison.InvariantCulture)); + AssertEqual(span.Equals("aaaa".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.Equals("aaaa", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.Equals("zzzz".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("zzzz", StringComparison.OrdinalIgnoreCase)); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup13(ReadOnlySpan span) + { + AssertEqual(span.SequenceEqual("AAdd".ToVar()), span.SequenceEqual("AAdd")); + AssertEqual(span.SequenceEqual("ZZdd".ToVar()), span.SequenceEqual("ZZdd")); + AssertEqual(span.SequenceEqual("xxdd".ToVar()), span.SequenceEqual("xxdd")); + AssertEqual(span.SequenceEqual("XXdd".ToVar()), span.SequenceEqual("XXdd")); + AssertEqual(span.SequenceEqual("dd\r\r".ToVar()), span.SequenceEqual("dd\r\r")); + AssertEqual(span.SequenceEqual("--xx".ToVar()), span.SequenceEqual("--xx")); + AssertEqual(span.SequenceEqual("\0\0bb".ToVar()), span.SequenceEqual("\0\0bb")); + AssertEqual(span.SequenceEqual("aaaж".ToVar()), span.SequenceEqual("aaaж")); + AssertEqual(span.SequenceEqual("abcd".ToVar()), span.SequenceEqual("abcd")); + AssertEqual(span.SequenceEqual("zZzв".ToVar()), span.SequenceEqual("zZzв")); + AssertEqual(span.SequenceEqual("ABCD".ToVar()), span.SequenceEqual("ABCD")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup14(ReadOnlySpan span) + { + AssertEqual(span.Equals("AAdd".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("AAdd", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("ZZdd".ToVar(), StringComparison.Ordinal), span.Equals("ZZdd", StringComparison.Ordinal)); + AssertEqual(span.Equals("xxdd".ToVar(), StringComparison.InvariantCulture), span.Equals("xxdd", StringComparison.InvariantCulture)); + AssertEqual(span.Equals("XXdd".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.Equals("XXdd", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.Equals("dd\r\r".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("dd\r\r", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("--xx".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("--xx", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("\0\0bb".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("\0\0bb", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("aaaж".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("aaaж", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("abcd".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.Equals("abcd", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.Equals("zZzв".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("zZzв", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.Equals("ABCD".ToVar(), StringComparison.OrdinalIgnoreCase), span.Equals("ABCD", StringComparison.OrdinalIgnoreCase)); + } + + public static IEnumerable Powerset(char[] source) + { + return Enumerable + .Range(0, 1 << (source.Length)) + .Select(index => source.Where((c, i) => (index & (1 << i)) != 0).ToArray()); + } + + public static int Main(string[] args) + { + var powerset = Powerset(new char[] + { + 'a', 'A', 'z', 'Z', '\r', '-', + 'ж', 'Ы', 'c', 'd', 'v', (char)0x20, + (char)0x9500, (char)0x0095, (char)0x9595 + }); + + foreach (var item in powerset) + { + ReadOnlySpan span = item.AsSpan(); + TestGroup1(span); + TestGroup2(span); + TestGroup3(span); + TestGroup4(span); + TestGroup5(span); + TestGroup6(span); + TestGroup7(span); + TestGroup8(span); + TestGroup9(span); + TestGroup10(span); + TestGroup11(span); + TestGroup12(span); + TestGroup13(span); + TestGroup14(span); + } + return s_ReturnCode; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static string ToVar(this string str) => str; + + private static void AssertEqual(bool expected, bool actual, [CallerLineNumber] int line = 0) + { + if (expected != actual) + { + Console.WriteLine($"ERROR: {expected} != {actual} L{line}"); + s_ReturnCode++; + } + } +} diff --git a/src/tests/JIT/opt/Unroll/Span_SequenceEqual_ConstString.csproj b/src/tests/JIT/opt/Unroll/Span_SequenceEqual_ConstString.csproj new file mode 100644 index 00000000000000..da51dfa3f43dbe --- /dev/null +++ b/src/tests/JIT/opt/Unroll/Span_SequenceEqual_ConstString.csproj @@ -0,0 +1,9 @@ + + + Exe + True + + + + + diff --git a/src/tests/JIT/opt/Unroll/Span_StartsWith_ConstString.cs b/src/tests/JIT/opt/Unroll/Span_StartsWith_ConstString.cs new file mode 100644 index 00000000000000..c1465af7595c0c --- /dev/null +++ b/src/tests/JIT/opt/Unroll/Span_StartsWith_ConstString.cs @@ -0,0 +1,301 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +public static class Program +{ + private static int s_ReturnCode = 100; + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup1(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("".ToVar()), span.StartsWith("")); + AssertEqual(span.StartsWith("a".ToVar()), span.StartsWith("a")); + AssertEqual(span.StartsWith("z".ToVar()), span.StartsWith("z")); + AssertEqual(span.StartsWith("A".ToVar()), span.StartsWith("A")); + AssertEqual(span.StartsWith("Z".ToVar()), span.StartsWith("Z")); + AssertEqual(span.StartsWith("x".ToVar()), span.StartsWith("x")); + AssertEqual(span.StartsWith("X".ToVar()), span.StartsWith("X")); + AssertEqual(span.StartsWith("\r".ToVar()), span.StartsWith("\r")); + AssertEqual(span.StartsWith("-".ToVar()), span.StartsWith("-")); + AssertEqual(span.StartsWith("\0".ToVar()), span.StartsWith("\0")); + AssertEqual(span.StartsWith("ж".ToVar()), span.StartsWith("ж")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup2(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.StartsWith("", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.StartsWith("a".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("a", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("z".ToVar(), StringComparison.Ordinal), span.StartsWith("z", StringComparison.Ordinal)); + AssertEqual(span.StartsWith("A".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("A", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("Z".ToVar(), StringComparison.InvariantCulture), span.StartsWith("Z", StringComparison.InvariantCulture)); + AssertEqual(span.StartsWith("x".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("x", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("X".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("X", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("\r".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.StartsWith("\r", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.StartsWith("-".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("-", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("\0".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("\0", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("ж".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("ж", StringComparison.OrdinalIgnoreCase)); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup3(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("aa".ToVar()), span.StartsWith("aa")); + AssertEqual(span.StartsWith("zz".ToVar()), span.StartsWith("zz")); + AssertEqual(span.StartsWith("AA".ToVar()), span.StartsWith("AA")); + AssertEqual(span.StartsWith("ZZ".ToVar()), span.StartsWith("ZZ")); + AssertEqual(span.StartsWith("xx".ToVar()), span.StartsWith("xx")); + AssertEqual(span.StartsWith("XX".ToVar()), span.StartsWith("XX")); + AssertEqual(span.StartsWith("\r\r".ToVar()), span.StartsWith("\r\r")); + AssertEqual(span.StartsWith("--".ToVar()), span.StartsWith("--")); + AssertEqual(span.StartsWith("\0\0".ToVar()), span.StartsWith("\0\0")); + AssertEqual(span.StartsWith("жж".ToVar()), span.StartsWith("жж")); + AssertEqual(span.StartsWith("va".ToVar()), span.StartsWith("va")); + AssertEqual(span.StartsWith("vz".ToVar()), span.StartsWith("vz")); + AssertEqual(span.StartsWith("vA".ToVar()), span.StartsWith("vA")); + AssertEqual(span.StartsWith("vZ".ToVar()), span.StartsWith("vZ")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup4(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("aa".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.StartsWith("aa", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.StartsWith("zz".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("zz", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("AA".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("AA", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("ZZ".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("ZZ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("xx".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.StartsWith("xx", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.StartsWith("XX".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("XX", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("\r\r".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("\r\r", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("--".ToVar(), StringComparison.Ordinal), span.StartsWith("--", StringComparison.Ordinal)); + AssertEqual(span.StartsWith("\0\0".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("\0\0", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("жж".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("жж", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("va".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("va", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("vz".ToVar(), StringComparison.InvariantCulture), span.StartsWith("vz", StringComparison.InvariantCulture)); + AssertEqual(span.StartsWith("vA".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("vA", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("vZ".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("vZ", StringComparison.OrdinalIgnoreCase)); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup5(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("vx".ToVar()), span.StartsWith("vx")); + AssertEqual(span.StartsWith("vX".ToVar()), span.StartsWith("vX")); + AssertEqual(span.StartsWith("v\r".ToVar()), span.StartsWith("v\r")); + AssertEqual(span.StartsWith("v-".ToVar()), span.StartsWith("v-")); + AssertEqual(span.StartsWith("v\0".ToVar()), span.StartsWith("v\0")); + AssertEqual(span.StartsWith("vж".ToVar()), span.StartsWith("vж")); + AssertEqual(span.StartsWith("aJ".ToVar()), span.StartsWith("aJ")); + AssertEqual(span.StartsWith("zJ".ToVar()), span.StartsWith("zJ")); + AssertEqual(span.StartsWith("AJ".ToVar()), span.StartsWith("AJ")); + AssertEqual(span.StartsWith("ZJ".ToVar()), span.StartsWith("ZJ")); + AssertEqual(span.StartsWith("xJ".ToVar()), span.StartsWith("xJ")); + AssertEqual(span.StartsWith("XJ".ToVar()), span.StartsWith("XJ")); + AssertEqual(span.StartsWith("\rJ".ToVar()), span.StartsWith("\rJ")); + AssertEqual(span.StartsWith("-J".ToVar()), span.StartsWith("-J")); + AssertEqual(span.StartsWith("\0J".ToVar()), span.StartsWith("\0J")); + AssertEqual(span.StartsWith("жJ".ToVar()), span.StartsWith("жJ")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup6(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("vx".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("vx", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("vX".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("vX", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("v\r".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("v\r", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("v-".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("v-", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("v\0".ToVar(), StringComparison.Ordinal), span.StartsWith("v\0", StringComparison.Ordinal)); + AssertEqual(span.StartsWith("vж".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("vж", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("aJ".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("aJ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("zJ".ToVar(), StringComparison.InvariantCulture), span.StartsWith("zJ", StringComparison.InvariantCulture)); + AssertEqual(span.StartsWith("AJ".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("AJ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("ZJ".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.StartsWith("ZJ", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.StartsWith("xJ".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("xJ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("XJ".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.StartsWith("XJ", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.StartsWith("\rJ".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("\rJ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("-J".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("-J", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("\0J".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("\0J", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("жJ".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("жJ", StringComparison.OrdinalIgnoreCase)); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup7(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("aaa".ToVar()), span.StartsWith("aaa")); + AssertEqual(span.StartsWith("zzz".ToVar()), span.StartsWith("zzz")); + AssertEqual(span.StartsWith("AAA".ToVar()), span.StartsWith("AAA")); + AssertEqual(span.StartsWith("ZZZ".ToVar()), span.StartsWith("ZZZ")); + AssertEqual(span.StartsWith("xxx".ToVar()), span.StartsWith("xxx")); + AssertEqual(span.StartsWith("XXX".ToVar()), span.StartsWith("XXX")); + AssertEqual(span.StartsWith("\r\r\r".ToVar()), span.StartsWith("\r\r\r")); + AssertEqual(span.StartsWith("---".ToVar()), span.StartsWith("---")); + AssertEqual(span.StartsWith("\0\0\0".ToVar()), span.StartsWith("\0\0\0")); + AssertEqual(span.StartsWith("жжж".ToVar()), span.StartsWith("жжж")); + AssertEqual(span.StartsWith("ava".ToVar()), span.StartsWith("ava")); + AssertEqual(span.StartsWith("zvz".ToVar()), span.StartsWith("zvz")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup8(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("aaa".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("aaa", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("zzz".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("zzz", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("AAA".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.StartsWith("AAA", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.StartsWith("ZZZ".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("ZZZ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("xxx".ToVar(), StringComparison.InvariantCulture), span.StartsWith("xxx", StringComparison.InvariantCulture)); + AssertEqual(span.StartsWith("XXX".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.StartsWith("XXX", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.StartsWith("\r\r\r".ToVar(), StringComparison.Ordinal), span.StartsWith("\r\r\r", StringComparison.Ordinal)); + AssertEqual(span.StartsWith("---".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("---", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("\0\0\0".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("\0\0\0", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("жжж".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("жжж", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("ava".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("ava", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("zvz".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("zvz", StringComparison.OrdinalIgnoreCase)); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup9(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("AvA".ToVar()), span.StartsWith("AvA")); + AssertEqual(span.StartsWith("ZvZ".ToVar()), span.StartsWith("ZvZ")); + AssertEqual(span.StartsWith("xvx".ToVar()), span.StartsWith("xvx")); + AssertEqual(span.StartsWith("XvX".ToVar()), span.StartsWith("XvX")); + AssertEqual(span.StartsWith("\rv\r".ToVar()), span.StartsWith("\rv\r")); + AssertEqual(span.StartsWith("-v-".ToVar()), span.StartsWith("-v-")); + AssertEqual(span.StartsWith("\0v\0".ToVar()), span.StartsWith("\0v\0")); + AssertEqual(span.StartsWith("жvж".ToVar()), span.StartsWith("жvж")); + AssertEqual(span.StartsWith("aaж".ToVar()), span.StartsWith("aaж")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup10(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("AvA".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("AvA", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("ZvZ".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("ZvZ", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("xvx".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.StartsWith("xvx", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.StartsWith("XvX".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.StartsWith("XvX", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.StartsWith("\rv\r".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("\rv\r", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("-v-".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("-v-", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("\0v\0".ToVar(), StringComparison.InvariantCulture), span.StartsWith("\0v\0", StringComparison.InvariantCulture)); + AssertEqual(span.StartsWith("жvж".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("жvж", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("aaж".ToVar(), StringComparison.Ordinal), span.StartsWith("aaж", StringComparison.Ordinal)); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup11(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("aaaa".ToVar()), span.StartsWith("aaaa")); + AssertEqual(span.StartsWith("zzzz".ToVar()), span.StartsWith("zzzz")); + AssertEqual(span.StartsWith("AAAA".ToVar()), span.StartsWith("AAAA")); + AssertEqual(span.StartsWith("ZZZZ".ToVar()), span.StartsWith("ZZZZ")); + AssertEqual(span.StartsWith("xxxx".ToVar()), span.StartsWith("xxxx")); + AssertEqual(span.StartsWith("XXXX".ToVar()), span.StartsWith("XXXX")); + AssertEqual(span.StartsWith("\r\r\r\r".ToVar()), span.StartsWith("\r\r\r\r")); + AssertEqual(span.StartsWith("----".ToVar()), span.StartsWith("----")); + AssertEqual(span.StartsWith("\0\0\0\0".ToVar()), span.StartsWith("\0\0\0\0")); + AssertEqual(span.StartsWith("жжжж".ToVar()), span.StartsWith("жжжж")); + AssertEqual(span.StartsWith("aaaa".ToVar()), span.StartsWith("aaaa")); + AssertEqual(span.StartsWith("zzzz".ToVar()), span.StartsWith("zzzz")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup12(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("aaaa".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("aaaa", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("zzzz".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.StartsWith("zzzz", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.StartsWith("AAAA".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("AAAA", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("ZZZZ".ToVar(), StringComparison.Ordinal), span.StartsWith("ZZZZ", StringComparison.Ordinal)); + AssertEqual(span.StartsWith("xxxx".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("xxxx", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("XXXX".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("XXXX", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("\r\r\r\r".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("\r\r\r\r", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("----".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("----", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("\0\0\0\0".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("\0\0\0\0", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("жжжж".ToVar(), StringComparison.InvariantCulture), span.StartsWith("жжжж", StringComparison.InvariantCulture)); + AssertEqual(span.StartsWith("aaaa".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.StartsWith("aaaa", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.StartsWith("zzzz".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("zzzz", StringComparison.OrdinalIgnoreCase)); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup13(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("AAdd".ToVar()), span.StartsWith("AAdd")); + AssertEqual(span.StartsWith("ZZdd".ToVar()), span.StartsWith("ZZdd")); + AssertEqual(span.StartsWith("xxdd".ToVar()), span.StartsWith("xxdd")); + AssertEqual(span.StartsWith("XXdd".ToVar()), span.StartsWith("XXdd")); + AssertEqual(span.StartsWith("dd\r\r".ToVar()), span.StartsWith("dd\r\r")); + AssertEqual(span.StartsWith("--xx".ToVar()), span.StartsWith("--xx")); + AssertEqual(span.StartsWith("\0\0bb".ToVar()), span.StartsWith("\0\0bb")); + AssertEqual(span.StartsWith("aaaж".ToVar()), span.StartsWith("aaaж")); + AssertEqual(span.StartsWith("abcd".ToVar()), span.StartsWith("abcd")); + AssertEqual(span.StartsWith("zZzв".ToVar()), span.StartsWith("zZzв")); + AssertEqual(span.StartsWith("ABCD".ToVar()), span.StartsWith("ABCD")); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup14(ReadOnlySpan span) + { + AssertEqual(span.StartsWith("AAdd".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("AAdd", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("ZZdd".ToVar(), StringComparison.Ordinal), span.StartsWith("ZZdd", StringComparison.Ordinal)); + AssertEqual(span.StartsWith("xxdd".ToVar(), StringComparison.InvariantCulture), span.StartsWith("xxdd", StringComparison.InvariantCulture)); + AssertEqual(span.StartsWith("XXdd".ToVar(), StringComparison.CurrentCultureIgnoreCase), span.StartsWith("XXdd", StringComparison.CurrentCultureIgnoreCase)); + AssertEqual(span.StartsWith("dd\r\r".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("dd\r\r", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("--xx".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("--xx", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("\0\0bb".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("\0\0bb", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("aaaж".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("aaaж", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("abcd".ToVar(), StringComparison.InvariantCultureIgnoreCase), span.StartsWith("abcd", StringComparison.InvariantCultureIgnoreCase)); + AssertEqual(span.StartsWith("zZzв".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("zZzв", StringComparison.OrdinalIgnoreCase)); + AssertEqual(span.StartsWith("ABCD".ToVar(), StringComparison.OrdinalIgnoreCase), span.StartsWith("ABCD", StringComparison.OrdinalIgnoreCase)); + } + + public static IEnumerable Powerset(char[] source) + { + return Enumerable + .Range(0, 1 << (source.Length)) + .Select(index => source.Where((c, i) => (index & (1 << i)) != 0).ToArray()); + } + + public static int Main(string[] args) + { + var powerset = Powerset(new char[] + { + 'a', 'A', 'z', 'Z', '\r', '-', + 'ж', 'Ы', 'c', 'd', 'v', (char)0x20, + (char)0x9500, (char)0x0095, (char)0x9595 + }); + + foreach (var item in powerset) + { + ReadOnlySpan span = item.AsSpan(); + TestGroup1(span); + TestGroup2(span); + TestGroup3(span); + TestGroup4(span); + TestGroup5(span); + TestGroup6(span); + TestGroup7(span); + TestGroup8(span); + TestGroup9(span); + TestGroup10(span); + TestGroup11(span); + TestGroup12(span); + TestGroup13(span); + TestGroup14(span); + } + return s_ReturnCode; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static string ToVar(this string str) => str; + + private static void AssertEqual(bool expected, bool actual, [CallerLineNumber] int line = 0) + { + if (expected != actual) + { + Console.WriteLine($"ERROR: {expected} != {actual} L{line}"); + s_ReturnCode++; + } + } +} diff --git a/src/tests/JIT/opt/Unroll/Span_StartsWith_ConstString.csproj b/src/tests/JIT/opt/Unroll/Span_StartsWith_ConstString.csproj new file mode 100644 index 00000000000000..b906c3193a7b01 --- /dev/null +++ b/src/tests/JIT/opt/Unroll/Span_StartsWith_ConstString.csproj @@ -0,0 +1,9 @@ + + + Exe + True + + + + +