From 389a1a81a2e3b056d84e4cdee37d3478fb10a828 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Thu, 24 Dec 2020 20:32:12 +0300 Subject: [PATCH 01/15] Optimize `span.StartsWith("cstr")` to `span.Length >= 4 && *(span._pointer) == ToHex("cstr")` --- src/coreclr/jit/importer.cpp | 137 ++++++++++++++++++ src/coreclr/jit/namedintrinsiclist.h | 2 + .../src/System/MemoryExtensions.cs | 1 + .../src/System/String.cs | 1 + 4 files changed, 141 insertions(+) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index aa75207070bd37..c7d8950e2e4189 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4318,6 +4318,129 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } +// TODO-CQ: Enable for 32bit +#ifdef TARGET_64BIT + case NI_System_MemoryExtensions_StartsWith: + { + // Looking for + // + // bool x = MemoryExtensions.StartsWith(arg0, String.op_Implicit("CNS_STR")); + // + // In order to optimize into + // + // bool x = arg0.Length >= 4 && *(arg0._pointer) == ToHexConst("CNS_STR"); + // + // TODO-CQ: Do the same for StartsWith + OrdinalIgnoreCase/InvariantIngoreCase + + GenTree* arg0 = impStackTop(1).val; + GenTree* arg1 = impStackTop(0).val; + if (arg1->OperIs(GT_RET_EXPR)) + { + GenTreeCall* strToSpanCall = arg1->AsRetExpr()->gtInlineCandidate->AsCall(); + if (!(strToSpanCall->gtFlags & CORINFO_FLG_JIT_INTRINSIC) || + (lookupNamedIntrinsic(strToSpanCall->gtCallMethHnd) != NI_System_String_op_Implicit)) + { + // strToSpanCall must be `ReadOnlySpan String.op_Implicit(String)` + break; + } + + if (strToSpanCall->gtCallArgs->GetNode()->OperIs(GT_CNS_STR)) + { + // For now we only support constant strings + break; + } + + GenTreeStrCon* cnsStr = strToSpanCall->gtCallArgs->GetNode()->AsStrCon(); + + // Grab the actual string literal from VM + int strLen = -1; + LPCWSTR str = info.compCompHnd->getStringLiteral(cnsStr->gtScpHnd, cnsStr->gtSconCPX, &strLen); + + if (strLen == 0) + { + // return true for `span.StartsWith("")` + retNode = gtNewIconNode(1); + strToSpanCall->ReplaceWith(gtNewNothingNode(), this); + impPopStack(); + impPopStack(); + break; + } + + if ((str == nullptr) || (strLen < 1)) + { + // getStringLiteral couldn't manage to get the literal (e.g. in dynamic context) + break; + } + + if (strLen != 4) + { + // TODO-CQ: Support 1-3 and emit SIMD for larger strings + } + + UINT64 strAsUlong = 0; + for (int i = 0; i < strLen; i++) + { + UINT64 strChar = str[i]; + if (strChar > '\x007f') + { + // str is not ASCII - bail out. + break; + } + strAsUlong |= (strChar << 16UL * i); + } + + // We're going to emit the following tree: + // + // \--* QMARK int + // +--* GE 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 arg0cls = gtGetStructHandle(arg0); + CORINFO_FIELD_HANDLE pointerHnd = info.compCompHnd->getFieldInClass(arg0cls, 0); + CORINFO_FIELD_HANDLE lengthHnd = info.compCompHnd->getFieldInClass(arg0cls, 1); + const unsigned pointerOffset = info.compCompHnd->getFieldOffset(pointerHnd); + const unsigned lengthOffset = info.compCompHnd->getFieldOffset(lengthHnd); + + GenTree* spanRef = arg0; + if (arg0->TypeIs(TYP_STRUCT)) + { + spanRef = gtNewOperNode(GT_ADDR, TYP_BYREF, arg0); + } + 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* indirCmp = gtNewOperNode(GT_EQ, TYP_INT, gtNewIndir(TYP_LONG, spanData), gtNewIconNode(strAsUlong, TYP_LONG)); + 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(GT_GE, 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); + retNode = gtNewLclvNode(tmp, TYP_INT); + + // We don't need strToSpanCall anymore, replace with No-op + strToSpanCall->ReplaceWith(gtNewNothingNode(), this); + impPopStack(); + impPopStack(); + break; + } + break; + } +#endif + case NI_System_Threading_Thread_get_ManagedThreadId: { if (opts.OptimizationEnabled() && impStackTop().val->OperIs(GT_RET_EXPR)) @@ -4831,6 +4954,20 @@ 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(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..db1d0395c58332 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -44,6 +44,8 @@ enum NamedIntrinsic : unsigned short NI_System_Type_IsAssignableFrom, NI_System_Type_IsAssignableTo, NI_System_Array_Clone, + NI_System_MemoryExtensions_StartsWith, + 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.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 86038795cef8d7..9968bb5b186491 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -1059,6 +1059,7 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(value)), /// Determines whether the specified sequence appears at the start of the span. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Intrinsic] public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan value) where T : IEquatable { int valueLength = value.Length; 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; From 22e2cc2a0c2e964fdb63c74363f05dbe6f89e236 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 25 Dec 2020 00:16:36 +0300 Subject: [PATCH 02/15] Optimize `span.StartsWith("cstr")` to `span.Length >= 4 && *(span._pointer) == ToHex("cstr")` --- src/coreclr/jit/importer.cpp | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index c7d8950e2e4189..13c8b014661ffe 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4324,11 +4324,11 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, { // Looking for // - // bool x = MemoryExtensions.StartsWith(arg0, String.op_Implicit("CNS_STR")); + // bool x = MemoryExtensions.StartsWith(arg0, String.op_Implicit("cstr")); // // In order to optimize into // - // bool x = arg0.Length >= 4 && *(arg0._pointer) == ToHexConst("CNS_STR"); + // bool x = arg0.Length >= 4 && *(arg0._pointer) == ToHexConst("cstr"); // // TODO-CQ: Do the same for StartsWith + OrdinalIgnoreCase/InvariantIngoreCase @@ -4344,7 +4344,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } - if (strToSpanCall->gtCallArgs->GetNode()->OperIs(GT_CNS_STR)) + if (!strToSpanCall->gtCallArgs->GetNode()->OperIs(GT_CNS_STR)) { // For now we only support constant strings break; @@ -4372,9 +4372,24 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } - if (strLen != 4) + var_types cmpType; + + if (strLen == 1) + { + cmpType = TYP_BYTE; + } + else if (strLen == 2) + { + cmpType = TYP_INT; + } + else if (strLen == 4) { - // TODO-CQ: Support 1-3 and emit SIMD for larger strings + cmpType = TYP_LONG; + } + else + { + // TODO-CQ: Support other size and emit SIMD for larger strings + break; } UINT64 strAsUlong = 0; @@ -4421,7 +4436,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, spanRef = impCloneExpr(spanRef, &spanRefClone, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, nullptr DEBUGARG("spanRef")); GenTree* spanData = gtNewFieldRef(TYP_BYREF, pointerHnd, spanRefClone, pointerOffset); - GenTree* indirCmp = gtNewOperNode(GT_EQ, TYP_INT, gtNewIndir(TYP_LONG, spanData), gtNewIconNode(strAsUlong, TYP_LONG)); + GenTree* indirCmp = gtNewOperNode(GT_EQ, TYP_INT, gtNewIndir(cmpType, spanData), gtNewIconNode(strAsUlong, cmpType)); 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(GT_GE, TYP_INT, spanLenField, gtNewIconNode(strLen)), colon); From 7e2437220d69445cbeb03edaefb48d04944ff613 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 25 Dec 2020 11:23:01 +0300 Subject: [PATCH 03/15] Extract into a separate function --- src/coreclr/jit/compiler.h | 1 + src/coreclr/jit/importer.cpp | 231 ++++++++++++++++++----------------- 2 files changed, 123 insertions(+), 109 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 1cdebfb9c3c8aa..6087faafa306d2 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3888,6 +3888,7 @@ class Compiler void impImportLeave(BasicBlock* block); void impResetLeaveBlock(BasicBlock* block, unsigned jmpAddr); GenTree* impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom); + GenTree* impUnrollSpanComparisonAgainstConst(GenTree* span, GenTree* constSpan, bool ignoreCase, bool startsWith); GenTree* impIntrinsic(GenTree* newobjThis, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 13c8b014661ffe..695fcf3fcdcb25 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4318,23 +4318,21 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } -// TODO-CQ: Enable for 32bit -#ifdef TARGET_64BIT case NI_System_MemoryExtensions_StartsWith: { - // Looking for + // We're looking for // // bool x = MemoryExtensions.StartsWith(arg0, String.op_Implicit("cstr")); // - // In order to optimize into + // In order to optimize it into // // bool x = arg0.Length >= 4 && *(arg0._pointer) == ToHexConst("cstr"); // - // TODO-CQ: Do the same for StartsWith + OrdinalIgnoreCase/InvariantIngoreCase - + // TODO: Do the same for StartsWith + OrdinalIgnoreCase/InvariantIngoreCase + // and SequenceEquals/SequenceEquals with OrdinalIgnoreCase/InvariantIngoreCase. GenTree* arg0 = impStackTop(1).val; GenTree* arg1 = impStackTop(0).val; - if (arg1->OperIs(GT_RET_EXPR)) + if (arg1->OperIs(GT_RET_EXPR) && (sig->numArgs == 2)) { GenTreeCall* strToSpanCall = arg1->AsRetExpr()->gtInlineCandidate->AsCall(); if (!(strToSpanCall->gtFlags & CORINFO_FLG_JIT_INTRINSIC) || @@ -4344,117 +4342,18 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } - if (!strToSpanCall->gtCallArgs->GetNode()->OperIs(GT_CNS_STR)) - { - // For now we only support constant strings - break; - } - - GenTreeStrCon* cnsStr = strToSpanCall->gtCallArgs->GetNode()->AsStrCon(); - - // Grab the actual string literal from VM - int strLen = -1; - LPCWSTR str = info.compCompHnd->getStringLiteral(cnsStr->gtScpHnd, cnsStr->gtSconCPX, &strLen); - - if (strLen == 0) + GenTree* newNode = impUnrollSpanComparisonAgainstConst(arg0, strToSpanCall->gtCallArgs->GetNode(), false, true); + if (newNode != nullptr) { - // return true for `span.StartsWith("")` - retNode = gtNewIconNode(1); + retNode = newNode; strToSpanCall->ReplaceWith(gtNewNothingNode(), this); impPopStack(); impPopStack(); break; } - - if ((str == nullptr) || (strLen < 1)) - { - // getStringLiteral couldn't manage to get the literal (e.g. in dynamic context) - break; - } - - var_types cmpType; - - if (strLen == 1) - { - cmpType = TYP_BYTE; - } - else if (strLen == 2) - { - cmpType = TYP_INT; - } - else if (strLen == 4) - { - cmpType = TYP_LONG; - } - else - { - // TODO-CQ: Support other size and emit SIMD for larger strings - break; - } - - UINT64 strAsUlong = 0; - for (int i = 0; i < strLen; i++) - { - UINT64 strChar = str[i]; - if (strChar > '\x007f') - { - // str is not ASCII - bail out. - break; - } - strAsUlong |= (strChar << 16UL * i); - } - - // We're going to emit the following tree: - // - // \--* QMARK int - // +--* GE 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 arg0cls = gtGetStructHandle(arg0); - CORINFO_FIELD_HANDLE pointerHnd = info.compCompHnd->getFieldInClass(arg0cls, 0); - CORINFO_FIELD_HANDLE lengthHnd = info.compCompHnd->getFieldInClass(arg0cls, 1); - const unsigned pointerOffset = info.compCompHnd->getFieldOffset(pointerHnd); - const unsigned lengthOffset = info.compCompHnd->getFieldOffset(lengthHnd); - - GenTree* spanRef = arg0; - if (arg0->TypeIs(TYP_STRUCT)) - { - spanRef = gtNewOperNode(GT_ADDR, TYP_BYREF, arg0); - } - 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* indirCmp = gtNewOperNode(GT_EQ, TYP_INT, gtNewIndir(cmpType, spanData), gtNewIconNode(strAsUlong, cmpType)); - 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(GT_GE, 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); - retNode = gtNewLclvNode(tmp, TYP_INT); - - // We don't need strToSpanCall anymore, replace with No-op - strToSpanCall->ReplaceWith(gtNewNothingNode(), this); - impPopStack(); - impPopStack(); - break; } break; } -#endif case NI_System_Threading_Thread_get_ManagedThreadId: { @@ -4711,6 +4610,120 @@ GenTree* Compiler::impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom) return nullptr; } +GenTree* Compiler::impUnrollSpanComparisonAgainstConst(GenTree* span, GenTree* constSpan, bool ignoreCase, bool startsWith) +{ +#ifdef TARGET_64BIT + // TODO: Implement ignoreCase support. + assert(!ignoreCase); + + if (!constSpan->OperIs(GT_CNS_STR)) + { + // For now we only support constant strings + return nullptr; + } + + GenTreeStrCon* cnsStr = constSpan->AsStrCon(); + + // 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) + { + return gtNewIconNode(1); + } + else + { + // TODO: Emit `span.Length == 0` + return nullptr; + } + } + if (strLen == 1) + { + cmpType = TYP_BYTE; + } + 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; + } + + UINT64 strAsUlong = 0; + for (int i = 0; i < strLen; i++) + { + UINT64 strChar = str[i]; + if (strChar > '\x007f') + { + // str is not ASCII - bail out. + return nullptr; + } + strAsUlong |= (strChar << 16UL * i); + } + + // We're going to emit the following tree: + // + // \--* QMARK int + // +--* GE int // or GE_EQ if it's not "startsWith" mode + // | +--* 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* indirCmp = gtNewOperNode(GT_EQ, TYP_INT, gtNewIndir(cmpType, spanData), gtNewIconNode(strAsUlong, cmpType)); + 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 bit + return nullptr; +#endif +} + GenTree* Compiler::impMathIntrinsic(CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, var_types callType, From a6ecf244071cd726a0061b989291a5a69f08b0e0 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 25 Dec 2020 11:38:06 +0300 Subject: [PATCH 04/15] Add SequenceEqual support --- src/coreclr/jit/importer.cpp | 22 +++++++++++++------ src/coreclr/jit/namedintrinsiclist.h | 1 + .../src/System/MemoryExtensions.cs | 4 +++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 695fcf3fcdcb25..c9fa7985e6ea9d 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4318,18 +4318,21 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } + case NI_System_MemoryExtensions_SequenceEqual: case NI_System_MemoryExtensions_StartsWith: { - // We're looking for + // We're looking for: // - // bool x = MemoryExtensions.StartsWith(arg0, String.op_Implicit("cstr")); + // bool x1 = arg0.StartsWith(String.op_Implicit("cstr")); + // bool x2 = arg0.SequenceEqual(arg0, String.op_Implicit("cstr")); // - // In order to optimize it into + // In order to optimize it into: // - // bool x = arg0.Length >= 4 && *(arg0._pointer) == ToHexConst("cstr"); + // bool x1 = arg0.Length >= 4 && *(arg0._pointer) == ToHexConst("cstr"); + // bool x2 = arg0.Length == 4 && *(arg0._pointer) == ToHexConst("cstr"); + // + // TODO: Do the same for OrdinalIgnoreCase/InvariantIngoreCase // - // TODO: Do the same for StartsWith + OrdinalIgnoreCase/InvariantIngoreCase - // and SequenceEquals/SequenceEquals with OrdinalIgnoreCase/InvariantIngoreCase. GenTree* arg0 = impStackTop(1).val; GenTree* arg1 = impStackTop(0).val; if (arg1->OperIs(GT_RET_EXPR) && (sig->numArgs == 2)) @@ -4342,7 +4345,8 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } - GenTree* newNode = impUnrollSpanComparisonAgainstConst(arg0, strToSpanCall->gtCallArgs->GetNode(), false, true); + bool startsWith = ni == NI_System_MemoryExtensions_SequenceEqual; + GenTree* newNode = impUnrollSpanComparisonAgainstConst(arg0, strToSpanCall->gtCallArgs->GetNode(), false, startsWith); if (newNode != nullptr) { retNode = newNode; @@ -4988,6 +4992,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) { result = NI_System_MemoryExtensions_StartsWith; } + else if (strcmp(methodName, "SequenceEqual") == 0) + { + result = NI_System_MemoryExtensions_SequenceEqual; + } } else if (strcmp(className, "String") == 0) { diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index db1d0395c58332..9ce3abcbbae42f 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -45,6 +45,7 @@ enum NamedIntrinsic : unsigned short NI_System_Type_IsAssignableTo, NI_System_Array_Clone, NI_System_MemoryExtensions_StartsWith, + NI_System_MemoryExtensions_SequenceEqual, NI_System_String_op_Implicit, // These are used by HWIntrinsics but are defined more generally diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 9968bb5b186491..df83b2fc03e37a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -991,6 +991,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 +1039,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,8 +1060,8 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(value)), /// /// Determines whether the specified sequence appears at the start of the span. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] [Intrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan value) where T : IEquatable { int valueLength = value.Length; From 3b1df76afda020ad8b676f286e1316f252e207b0 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 25 Dec 2020 13:45:04 +0300 Subject: [PATCH 05/15] Add "ignoreCase" support, fix a bug, formatting --- src/coreclr/jit/importer.cpp | 64 +++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index c9fa7985e6ea9d..c50787805d61ef 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4322,7 +4322,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, case NI_System_MemoryExtensions_StartsWith: { // We're looking for: - // + // // bool x1 = arg0.StartsWith(String.op_Implicit("cstr")); // bool x2 = arg0.SequenceEqual(arg0, String.op_Implicit("cstr")); // @@ -4345,8 +4345,9 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } - bool startsWith = ni == NI_System_MemoryExtensions_SequenceEqual; - GenTree* newNode = impUnrollSpanComparisonAgainstConst(arg0, strToSpanCall->gtCallArgs->GetNode(), false, startsWith); + bool startsWith = ni == NI_System_MemoryExtensions_StartsWith; + GenTree* newNode = impUnrollSpanComparisonAgainstConst(arg0, strToSpanCall->gtCallArgs->GetNode(), + false, startsWith); if (newNode != nullptr) { retNode = newNode; @@ -4614,12 +4615,12 @@ GenTree* Compiler::impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom) return nullptr; } -GenTree* Compiler::impUnrollSpanComparisonAgainstConst(GenTree* span, GenTree* constSpan, bool ignoreCase, bool startsWith) +GenTree* Compiler::impUnrollSpanComparisonAgainstConst(GenTree* span, + GenTree* constSpan, + bool ignoreCase, + bool startsWith) { #ifdef TARGET_64BIT - // TODO: Implement ignoreCase support. - assert(!ignoreCase); - if (!constSpan->OperIs(GT_CNS_STR)) { // For now we only support constant strings @@ -4647,7 +4648,7 @@ GenTree* Compiler::impUnrollSpanComparisonAgainstConst(GenTree* span, GenTree* c } if (strLen == 1) { - cmpType = TYP_BYTE; + cmpType = TYP_SHORT; } else if (strLen == 2) { @@ -4669,7 +4670,9 @@ GenTree* Compiler::impUnrollSpanComparisonAgainstConst(GenTree* span, GenTree* c return nullptr; } - UINT64 strAsUlong = 0; + bool canBeLowercased = false; + UINT64 strAsUlong = 0; + for (int i = 0; i < strLen; i++) { UINT64 strChar = str[i]; @@ -4678,13 +4681,28 @@ GenTree* Compiler::impUnrollSpanComparisonAgainstConst(GenTree* span, GenTree* c // str is not ASCII - bail out. return nullptr; } + if ((strChar < 'A') || (strChar > 'z')) + { + canBeLowercased = true; + } strAsUlong |= (strChar << 16UL * i); } + if (ignoreCase) + { + if (!canBeLowercased) + { + // For this case we can't just do "x | 0x0020002000200020UL" + // TODO: Still can be implemented, see UInt64OrdinalIgnoreCaseAscii + return nullptr; + } + strAsUlong |= 0x0020002000200020UL; + } + // We're going to emit the following tree: // // \--* QMARK int - // +--* GE int // or GE_EQ if it's not "startsWith" mode + // +--* GE/EQ int // | +--* FIELD int Span._length // | \--* CNS_INT int %strLen% // \--* COLON int @@ -4710,20 +4728,34 @@ GenTree* Compiler::impUnrollSpanComparisonAgainstConst(GenTree* span, GenTree* c // 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")); + 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(strAsUlong, cmpType); - GenTree* spanData = gtNewFieldRef(TYP_BYREF, pointerHnd, spanRefClone, pointerOffset); - GenTree* indirCmp = gtNewOperNode(GT_EQ, TYP_INT, gtNewIndir(cmpType, spanData), gtNewIconNode(strAsUlong, cmpType)); + if (ignoreCase) + { + // Set "is lower" bits in all chars + spanDataIndir = gtNewOperNode(GT_OR, cmpType, spanDataIndir, gtNewIconNode(0x0020002000200020UL, cmpType)); + } + + // TODO: for length == 3 (not supported yet) we need to do two indir cmp ops + 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); + 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 bit + // TODO: Enable for 32 at least for length [0..2] return nullptr; #endif } From 24c33e97ad8826876ae00415373f3fcf58d9b9b2 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 25 Dec 2020 13:48:18 +0300 Subject: [PATCH 06/15] Fix "ignoreCase" --- src/coreclr/jit/importer.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index c50787805d61ef..f54f0753b6e6c4 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4670,7 +4670,7 @@ GenTree* Compiler::impUnrollSpanComparisonAgainstConst(GenTree* span, return nullptr; } - bool canBeLowercased = false; + bool canBeLowercased = true; UINT64 strAsUlong = 0; for (int i = 0; i < strLen; i++) @@ -4683,7 +4683,8 @@ GenTree* Compiler::impUnrollSpanComparisonAgainstConst(GenTree* span, } if ((strChar < 'A') || (strChar > 'z')) { - canBeLowercased = true; + // e.g. ('-' | 0x20) == ('\r' | 0x20) which is not correct. + canBeLowercased = false; } strAsUlong |= (strChar << 16UL * i); } From d465e60e64e400647d99cf172f1f77f4ead06c99 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 25 Dec 2020 13:57:48 +0300 Subject: [PATCH 07/15] Clean up --- src/coreclr/jit/compiler.h | 6 +++++- src/coreclr/jit/importer.cpp | 27 ++++++++++++--------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 6087faafa306d2..003f95d06cdc07 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3888,7 +3888,11 @@ class Compiler void impImportLeave(BasicBlock* block); void impResetLeaveBlock(BasicBlock* block, unsigned jmpAddr); GenTree* impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom); - GenTree* impUnrollSpanComparisonAgainstConst(GenTree* span, GenTree* constSpan, bool ignoreCase, bool startsWith); + 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/importer.cpp b/src/coreclr/jit/importer.cpp index f54f0753b6e6c4..296ce01b2850df 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4345,9 +4345,14 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } - bool startsWith = ni == NI_System_MemoryExtensions_StartsWith; - GenTree* newNode = impUnrollSpanComparisonAgainstConst(arg0, strToSpanCall->gtCallArgs->GetNode(), - false, startsWith); + 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, false, startsWith); if (newNode != nullptr) { retNode = newNode; @@ -4615,20 +4620,12 @@ GenTree* Compiler::impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom) return nullptr; } -GenTree* Compiler::impUnrollSpanComparisonAgainstConst(GenTree* span, - GenTree* constSpan, - bool ignoreCase, - bool startsWith) +GenTree* Compiler::impUnrollSpanComparisonWithStrCon(GenTree* span, + GenTreeStrCon* cnsStr, + bool ignoreCase, + bool startsWith) { #ifdef TARGET_64BIT - if (!constSpan->OperIs(GT_CNS_STR)) - { - // For now we only support constant strings - return nullptr; - } - - GenTreeStrCon* cnsStr = constSpan->AsStrCon(); - // Grab the actual string literal from VM int strLen = -1; LPCWSTR str = info.compCompHnd->getStringLiteral(cnsStr->gtScpHnd, cnsStr->gtSconCPX, &strLen); From d1e5931de50264c5330702887aea0c03bd9ac861 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 25 Dec 2020 14:22:12 +0300 Subject: [PATCH 08/15] Formatting --- src/coreclr/jit/compiler.h | 5 +---- src/coreclr/jit/importer.cpp | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 003f95d06cdc07..62ce94464cc9fe 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3888,10 +3888,7 @@ 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* impUnrollSpanComparisonWithStrCon(GenTree* span, GenTreeStrCon* cnsStr, bool ignoreCase, bool startsWith); GenTree* impIntrinsic(GenTree* newobjThis, CORINFO_CLASS_HANDLE clsHnd, diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 296ce01b2850df..4880fe074ddd7e 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4730,7 +4730,7 @@ GenTree* Compiler::impUnrollSpanComparisonWithStrCon(GenTree* span, 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); + GenTree* spanDataIndir = gtNewIndir(cmpType, spanData); GenTreeIntCon* constStrAsIntCon = gtNewIconNode(strAsUlong, cmpType); if (ignoreCase) From 0561cac8fd96a33ebba3d5a08e0f3f9d6b3e3fa1 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 25 Dec 2020 17:42:04 +0300 Subject: [PATCH 09/15] Add tests --- .../Unroll/Span_SequenceEqual_ConstString.cs | 173 ++++++++++++++++++ .../Span_SequenceEqual_ConstString.csproj | 9 + .../opt/Unroll/Span_StartsWith_ConstString.cs | 173 ++++++++++++++++++ .../Unroll/Span_StartsWith_ConstString.csproj | 9 + 4 files changed, 364 insertions(+) create mode 100644 src/tests/JIT/opt/Unroll/Span_SequenceEqual_ConstString.cs create mode 100644 src/tests/JIT/opt/Unroll/Span_SequenceEqual_ConstString.csproj create mode 100644 src/tests/JIT/opt/Unroll/Span_StartsWith_ConstString.cs create mode 100644 src/tests/JIT/opt/Unroll/Span_StartsWith_ConstString.csproj 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..4addb1e85cbad2 --- /dev/null +++ b/src/tests/JIT/opt/Unroll/Span_SequenceEqual_ConstString.cs @@ -0,0 +1,173 @@ +// 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.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 TestGroup3(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 TestGroup4(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 TestGroup5(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 TestGroup6(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 TestGroup7(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")); + } + + public static IEnumerable Permutations(char[] source) + { + return Enumerable.Range(0, 1 << (source.Length)).Select(index => source.Where((v, i) => (index & (1 << i)) != 0).ToArray()); + } + + public static int Main(string[] args) + { + var allPermutations = Permutations(new char[] + { + 'a', 'A', 'z', 'Z', 'x', '\r', '\n', + '-', 'ж', 'Ы', 'c', 'd', 'e', 'v', + (char)0x9000, (char)0x0090, (char)0x9090, + (char)0x2000, (char)0x0020, (char)0x2020 + }); + + foreach (var item in allPermutations) + { + ReadOnlySpan span = item.AsSpan(); + TestGroup1(span); + TestGroup2(span); + TestGroup3(span); + TestGroup4(span); + TestGroup5(span); + TestGroup6(span); + TestGroup7(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..90568f13096dd8 --- /dev/null +++ b/src/tests/JIT/opt/Unroll/Span_StartsWith_ConstString.cs @@ -0,0 +1,173 @@ +// 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("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 TestGroup3(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 TestGroup4(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 TestGroup5(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 TestGroup6(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 TestGroup7(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")); + } + + public static IEnumerable Permutations(char[] source) + { + return Enumerable.Range(0, 1 << (source.Length)).Select(index => source.Where((v, i) => (index & (1 << i)) != 0).ToArray()); + } + + public static int Main(string[] args) + { + var allPermutations = Permutations(new char[] + { + 'a', 'A', 'z', 'Z', 'x', '\r', '\n', + '-', 'ж', 'Ы', 'c', 'd', 'e', 'v', + (char)0x9000, (char)0x0090, (char)0x9090, + (char)0x2000, (char)0x0020, (char)0x2020 + }); + + foreach (var item in allPermutations) + { + ReadOnlySpan span = item.AsSpan(); + TestGroup1(span); + TestGroup2(span); + TestGroup3(span); + TestGroup4(span); + TestGroup5(span); + TestGroup6(span); + TestGroup7(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 + + + + + From 1f25cafbd24ac995fd30670219a16b70c425ca82 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 25 Dec 2020 23:11:18 +0300 Subject: [PATCH 10/15] Cover more methods, add ignore case support. --- src/coreclr/jit/importer.cpp | 88 ++++++++++++++++--- src/coreclr/jit/namedintrinsiclist.h | 3 + .../System/MemoryExtensions.Globalization.cs | 4 + 3 files changed, 83 insertions(+), 12 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 4880fe074ddd7e..6f14fab218ca83 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4318,9 +4318,14 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } + case NI_System_MemoryExtensions_Equals: + case NI_System_MemoryExtensions_EqualsOrdinal: + case NI_System_MemoryExtensions_EqualsOrdinalIgnoreCase: 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")); @@ -4331,11 +4336,49 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, // bool x1 = arg0.Length >= 4 && *(arg0._pointer) == ToHexConst("cstr"); // bool x2 = arg0.Length == 4 && *(arg0._pointer) == ToHexConst("cstr"); // - // TODO: Do the same for OrdinalIgnoreCase/InvariantIngoreCase - // - GenTree* arg0 = impStackTop(1).val; - GenTree* arg1 = impStackTop(0).val; - if (arg1->OperIs(GT_RET_EXPR) && (sig->numArgs == 2)) + GenTree* arg0 = impStackTop(sig->numArgs - 1).val; + GenTree* arg1 = impStackTop(sig->numArgs - 2).val; + bool ignoreCase = (ni == NI_System_MemoryExtensions_EqualsOrdinalIgnoreCase); + + if ((sig->numArgs == 3) && impStackTop(0).val->IsCnsIntOrI()) + { + assert((ni == NI_System_MemoryExtensions_Equals) || + (ni == NI_System_MemoryExtensions_StartsWith)); + + // See StringComparison.cs + const int CurrentCulture = 0; + const int CurrentCultureIgnoreCase = 1; + const int InvariantCulture = 2; + const int InvariantCultureIgnoreCase = 3; + const int Ordinal = 4; + const int OrdinalIgnoreCase = 5; + + int mode = (int)impStackTop(0).val->AsIntCon()->IconValue(); + if ((mode == InvariantCulture) || (mode == Ordinal)) + { + ignoreCase = false; + } + else if ((mode == InvariantCultureIgnoreCase) || (mode == OrdinalIgnoreCase)) + { + ignoreCase = true; + } + else + { + assert((mode == CurrentCulture) || (mode == CurrentCultureIgnoreCase)); + return nullptr; + } + } + else if (sig->numArgs == 3) + { + // Comparison mode is not a constant. + return nullptr; + } + else + { + assert(sig->numArgs == 2); + } + + if (arg1->OperIs(GT_RET_EXPR)) { GenTreeCall* strToSpanCall = arg1->AsRetExpr()->gtInlineCandidate->AsCall(); if (!(strToSpanCall->gtFlags & CORINFO_FLG_JIT_INTRINSIC) || @@ -4352,13 +4395,15 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, GenTreeStrCon* strCon = strToSpanCall->gtCallArgs->GetNode()->AsStrCon(); bool startsWith = (ni == NI_System_MemoryExtensions_StartsWith); - GenTree* newNode = impUnrollSpanComparisonWithStrCon(arg0, strCon, false, startsWith); + GenTree* newNode = impUnrollSpanComparisonWithStrCon(arg0, strCon, ignoreCase, startsWith); if (newNode != nullptr) { retNode = newNode; strToSpanCall->ReplaceWith(gtNewNothingNode(), this); - impPopStack(); - impPopStack(); + for (unsigned i = 0; i < sig->numArgs; i++) + { + impPopStack(); + } break; } } @@ -4635,6 +4680,7 @@ GenTree* Compiler::impUnrollSpanComparisonWithStrCon(GenTree* span, { if (startsWith) { + // Any span starts with "", return true return gtNewIconNode(1); } else @@ -4643,7 +4689,7 @@ GenTree* Compiler::impUnrollSpanComparisonWithStrCon(GenTree* span, return nullptr; } } - if (strLen == 1) + else if (strLen == 1) { cmpType = TYP_SHORT; } @@ -4690,8 +4736,11 @@ GenTree* Compiler::impUnrollSpanComparisonWithStrCon(GenTree* span, { if (!canBeLowercased) { - // For this case we can't just do "x | 0x0020002000200020UL" - // TODO: Still can be implemented, see UInt64OrdinalIgnoreCaseAscii + // TODO: Implement logic from UInt64OrdinalIgnoreCaseAscii + // + // bool x = (((IND ^ strAsUlong)<<2) & + // (((strAsUlong+0x0005000500050005ul)|0x00A000A000A000A0ul)+0x001A001A001A001Aul)|0xFF7FFF7FFF7FFF7Ful)==0; + // return nullptr; } strAsUlong |= 0x0020002000200020UL; @@ -4739,7 +4788,10 @@ GenTree* Compiler::impUnrollSpanComparisonWithStrCon(GenTree* span, spanDataIndir = gtNewOperNode(GT_OR, cmpType, spanDataIndir, gtNewIconNode(0x0020002000200020UL, cmpType)); } - // TODO: for length == 3 (not supported yet) we need to do two indir cmp ops + // 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)); @@ -5026,6 +5078,18 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) { result = NI_System_MemoryExtensions_SequenceEqual; } + else if (strcmp(methodName, "Equals") == 0) + { + result = NI_System_MemoryExtensions_Equals; + } + else if (strcmp(methodName, "EqualsOrdinal") == 0) + { + result = NI_System_MemoryExtensions_EqualsOrdinal; + } + else if (strcmp(methodName, "EqualsOrdinalIgnoreCase") == 0) + { + result = NI_System_MemoryExtensions_EqualsOrdinalIgnoreCase; + } } else if (strcmp(className, "String") == 0) { diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index 9ce3abcbbae42f..ab9c251c1ebc54 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -44,6 +44,9 @@ enum NamedIntrinsic : unsigned short NI_System_Type_IsAssignableFrom, NI_System_Type_IsAssignableTo, NI_System_Array_Clone, + NI_System_MemoryExtensions_Equals, + NI_System_MemoryExtensions_EqualsOrdinal, + NI_System_MemoryExtensions_EqualsOrdinalIgnoreCase, NI_System_MemoryExtensions_StartsWith, NI_System_MemoryExtensions_SequenceEqual, NI_System_String_op_Implicit, 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..f3f419d4821d9c 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); @@ -66,6 +67,7 @@ public static bool Equals(this ReadOnlySpan span, ReadOnlySpan other } } + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool EqualsOrdinal(this ReadOnlySpan span, ReadOnlySpan value) { @@ -76,6 +78,7 @@ internal static bool EqualsOrdinal(this ReadOnlySpan span, ReadOnlySpan span, ReadOnlySpan value) { @@ -331,6 +334,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); From 8cdda9bed6565822f9a162862530d2c02be7b082 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 25 Dec 2020 23:30:27 +0300 Subject: [PATCH 11/15] Remove redundant intrinsics --- src/coreclr/jit/importer.cpp | 12 +----------- src/coreclr/jit/namedintrinsiclist.h | 2 -- .../src/System/MemoryExtensions.Globalization.cs | 2 -- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 6f14fab218ca83..852f6b9f36cd01 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4319,8 +4319,6 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, } case NI_System_MemoryExtensions_Equals: - case NI_System_MemoryExtensions_EqualsOrdinal: - case NI_System_MemoryExtensions_EqualsOrdinalIgnoreCase: case NI_System_MemoryExtensions_SequenceEqual: case NI_System_MemoryExtensions_StartsWith: { @@ -4338,7 +4336,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, // GenTree* arg0 = impStackTop(sig->numArgs - 1).val; GenTree* arg1 = impStackTop(sig->numArgs - 2).val; - bool ignoreCase = (ni == NI_System_MemoryExtensions_EqualsOrdinalIgnoreCase); + bool ignoreCase = false; if ((sig->numArgs == 3) && impStackTop(0).val->IsCnsIntOrI()) { @@ -5082,14 +5080,6 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) { result = NI_System_MemoryExtensions_Equals; } - else if (strcmp(methodName, "EqualsOrdinal") == 0) - { - result = NI_System_MemoryExtensions_EqualsOrdinal; - } - else if (strcmp(methodName, "EqualsOrdinalIgnoreCase") == 0) - { - result = NI_System_MemoryExtensions_EqualsOrdinalIgnoreCase; - } } else if (strcmp(className, "String") == 0) { diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index ab9c251c1ebc54..772314e7271e1b 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -45,8 +45,6 @@ enum NamedIntrinsic : unsigned short NI_System_Type_IsAssignableTo, NI_System_Array_Clone, NI_System_MemoryExtensions_Equals, - NI_System_MemoryExtensions_EqualsOrdinal, - NI_System_MemoryExtensions_EqualsOrdinalIgnoreCase, NI_System_MemoryExtensions_StartsWith, NI_System_MemoryExtensions_SequenceEqual, NI_System_String_op_Implicit, 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 f3f419d4821d9c..3876637a3e9667 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs @@ -67,7 +67,6 @@ public static bool Equals(this ReadOnlySpan span, ReadOnlySpan other } } - [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool EqualsOrdinal(this ReadOnlySpan span, ReadOnlySpan value) { @@ -78,7 +77,6 @@ internal static bool EqualsOrdinal(this ReadOnlySpan span, ReadOnlySpan span, ReadOnlySpan value) { From 790df253d8aadf938005da40d726e3ce54afb8d9 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 25 Dec 2020 23:55:12 +0300 Subject: [PATCH 12/15] Handle "..".AsSpan() --- src/coreclr/jit/importer.cpp | 17 ++++++++++++++--- src/coreclr/jit/namedintrinsiclist.h | 1 + .../src/System/MemoryExtensions.cs | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 852f6b9f36cd01..7ee0e75351462d 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4379,13 +4379,20 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, if (arg1->OperIs(GT_RET_EXPR)) { GenTreeCall* strToSpanCall = arg1->AsRetExpr()->gtInlineCandidate->AsCall(); - if (!(strToSpanCall->gtFlags & CORINFO_FLG_JIT_INTRINSIC) || - (lookupNamedIntrinsic(strToSpanCall->gtCallMethHnd) != NI_System_String_op_Implicit)) + if (!(strToSpanCall->gtFlags & CORINFO_FLG_JIT_INTRINSIC)) { - // strToSpanCall must be `ReadOnlySpan String.op_Implicit(String)` + // 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; @@ -5080,6 +5087,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) { result = NI_System_MemoryExtensions_Equals; } + else if (strcmp(methodName, "AsSpan") == 0) + { + result = NI_System_MemoryExtensions_AsSpan; + } } else if (strcmp(className, "String") == 0) { diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index 772314e7271e1b..ed5cd19c60795c 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -44,6 +44,7 @@ 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, diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index df83b2fc03e37a..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) { From 3d2ded270cc6db6df4527154d6f612d94db56a07 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 26 Dec 2020 00:58:59 +0300 Subject: [PATCH 13/15] More tests, formatting --- src/coreclr/jit/importer.cpp | 11 +- .../Unroll/Span_SequenceEqual_ConstString.cs | 158 ++++++++++++++++-- .../opt/Unroll/Span_StartsWith_ConstString.cs | 158 ++++++++++++++++-- 3 files changed, 292 insertions(+), 35 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 7ee0e75351462d..3eb9470c9f92a8 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4344,13 +4344,14 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, (ni == NI_System_MemoryExtensions_StartsWith)); // See StringComparison.cs - const int CurrentCulture = 0; - const int CurrentCultureIgnoreCase = 1; - const int InvariantCulture = 2; + const int CurrentCulture = 0; + const int CurrentCultureIgnoreCase = 1; + const int InvariantCulture = 2; const int InvariantCultureIgnoreCase = 3; - const int Ordinal = 4; - const int OrdinalIgnoreCase = 5; + const int Ordinal = 4; + const int OrdinalIgnoreCase = 5; + // Fetch mode from the last argument. int mode = (int)impStackTop(0).val->AsIntCon()->IconValue(); if ((mode == InvariantCulture) || (mode == Ordinal)) { diff --git a/src/tests/JIT/opt/Unroll/Span_SequenceEqual_ConstString.cs b/src/tests/JIT/opt/Unroll/Span_SequenceEqual_ConstString.cs index 4addb1e85cbad2..42893a674c8ffd 100644 --- a/src/tests/JIT/opt/Unroll/Span_SequenceEqual_ConstString.cs +++ b/src/tests/JIT/opt/Unroll/Span_SequenceEqual_ConstString.cs @@ -28,6 +28,22 @@ private static void TestGroup1(ReadOnlySpan span) [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")); @@ -46,7 +62,26 @@ private static void TestGroup2(ReadOnlySpan span) } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static void TestGroup3(ReadOnlySpan span) + 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")); @@ -67,7 +102,28 @@ private static void TestGroup3(ReadOnlySpan span) } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static void TestGroup4(ReadOnlySpan span) + 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")); @@ -84,7 +140,24 @@ private static void TestGroup4(ReadOnlySpan span) } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static void TestGroup5(ReadOnlySpan span) + 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")); @@ -98,7 +171,21 @@ private static void TestGroup5(ReadOnlySpan span) } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static void TestGroup6(ReadOnlySpan span) + 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")); @@ -115,7 +202,24 @@ private static void TestGroup6(ReadOnlySpan span) } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static void TestGroup7(ReadOnlySpan span) + 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")); @@ -130,22 +234,39 @@ private static void TestGroup7(ReadOnlySpan span) AssertEqual(span.SequenceEqual("ABCD".ToVar()), span.SequenceEqual("ABCD")); } - public static IEnumerable Permutations(char[] source) + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup14(ReadOnlySpan span) { - return Enumerable.Range(0, 1 << (source.Length)).Select(index => source.Where((v, i) => (index & (1 << i)) != 0).ToArray()); + 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 allPermutations = Permutations(new char[] - { - 'a', 'A', 'z', 'Z', 'x', '\r', '\n', - '-', 'ж', 'Ы', 'c', 'd', 'e', 'v', - (char)0x9000, (char)0x0090, (char)0x9090, - (char)0x2000, (char)0x0020, (char)0x2020 - }); + 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 allPermutations) + foreach (var item in powerset) { ReadOnlySpan span = item.AsSpan(); TestGroup1(span); @@ -155,6 +276,13 @@ public static int Main(string[] args) TestGroup5(span); TestGroup6(span); TestGroup7(span); + TestGroup8(span); + TestGroup9(span); + TestGroup10(span); + TestGroup11(span); + TestGroup12(span); + TestGroup13(span); + TestGroup14(span); } return s_ReturnCode; } diff --git a/src/tests/JIT/opt/Unroll/Span_StartsWith_ConstString.cs b/src/tests/JIT/opt/Unroll/Span_StartsWith_ConstString.cs index 90568f13096dd8..c1465af7595c0c 100644 --- a/src/tests/JIT/opt/Unroll/Span_StartsWith_ConstString.cs +++ b/src/tests/JIT/opt/Unroll/Span_StartsWith_ConstString.cs @@ -28,6 +28,22 @@ private static void TestGroup1(ReadOnlySpan span) [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")); @@ -46,7 +62,26 @@ private static void TestGroup2(ReadOnlySpan span) } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static void TestGroup3(ReadOnlySpan span) + 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")); @@ -67,7 +102,28 @@ private static void TestGroup3(ReadOnlySpan span) } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static void TestGroup4(ReadOnlySpan span) + 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")); @@ -84,7 +140,24 @@ private static void TestGroup4(ReadOnlySpan span) } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static void TestGroup5(ReadOnlySpan span) + 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")); @@ -98,7 +171,21 @@ private static void TestGroup5(ReadOnlySpan span) } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static void TestGroup6(ReadOnlySpan span) + 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")); @@ -115,7 +202,24 @@ private static void TestGroup6(ReadOnlySpan span) } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static void TestGroup7(ReadOnlySpan span) + 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")); @@ -130,22 +234,39 @@ private static void TestGroup7(ReadOnlySpan span) AssertEqual(span.StartsWith("ABCD".ToVar()), span.StartsWith("ABCD")); } - public static IEnumerable Permutations(char[] source) + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static void TestGroup14(ReadOnlySpan span) { - return Enumerable.Range(0, 1 << (source.Length)).Select(index => source.Where((v, i) => (index & (1 << i)) != 0).ToArray()); + 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 allPermutations = Permutations(new char[] - { - 'a', 'A', 'z', 'Z', 'x', '\r', '\n', - '-', 'ж', 'Ы', 'c', 'd', 'e', 'v', - (char)0x9000, (char)0x0090, (char)0x9090, - (char)0x2000, (char)0x0020, (char)0x2020 - }); + 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 allPermutations) + foreach (var item in powerset) { ReadOnlySpan span = item.AsSpan(); TestGroup1(span); @@ -155,6 +276,13 @@ public static int Main(string[] args) TestGroup5(span); TestGroup6(span); TestGroup7(span); + TestGroup8(span); + TestGroup9(span); + TestGroup10(span); + TestGroup11(span); + TestGroup12(span); + TestGroup13(span); + TestGroup14(span); } return s_ReturnCode; } From 27232d8e7e26923045c2db9f8753c55f910eb785 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 26 Dec 2020 15:42:41 +0300 Subject: [PATCH 14/15] Fix failing tests --- src/coreclr/jit/compiler.hpp | 16 +++++++++++++++ src/coreclr/jit/importer.cpp | 38 ++++++++++++++++++------------------ 2 files changed, 35 insertions(+), 19 deletions(-) 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 3eb9470c9f92a8..fd4f667e6f46e8 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4340,8 +4340,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, if ((sig->numArgs == 3) && impStackTop(0).val->IsCnsIntOrI()) { - assert((ni == NI_System_MemoryExtensions_Equals) || - (ni == NI_System_MemoryExtensions_StartsWith)); + assert((ni == NI_System_MemoryExtensions_Equals) || (ni == NI_System_MemoryExtensions_StartsWith)); // See StringComparison.cs const int CurrentCulture = 0; @@ -4353,17 +4352,19 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, // Fetch mode from the last argument. int mode = (int)impStackTop(0).val->AsIntCon()->IconValue(); - if ((mode == InvariantCulture) || (mode == Ordinal)) + + if (mode == OrdinalIgnoreCase) { - ignoreCase = false; + ignoreCase = true; } - else if ((mode == InvariantCultureIgnoreCase) || (mode == OrdinalIgnoreCase)) + else if (mode == Ordinal) { - ignoreCase = true; + ignoreCase = false; } else { - assert((mode == CurrentCulture) || (mode == CurrentCultureIgnoreCase)); + // This mode is not supported. + assert(mode <= OrdinalIgnoreCase); return nullptr; } } @@ -4372,10 +4373,6 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, // Comparison mode is not a constant. return nullptr; } - else - { - assert(sig->numArgs == 2); - } if (arg1->OperIs(GT_RET_EXPR)) { @@ -4719,7 +4716,7 @@ GenTree* Compiler::impUnrollSpanComparisonWithStrCon(GenTree* span, return nullptr; } - bool canBeLowercased = true; + bool allAreLetters = true; UINT64 strAsUlong = 0; for (int i = 0; i < strLen; i++) @@ -4730,17 +4727,18 @@ GenTree* Compiler::impUnrollSpanComparisonWithStrCon(GenTree* span, // str is not ASCII - bail out. return nullptr; } - if ((strChar < 'A') || (strChar > 'z')) + + bool isLetter = ((strChar >= 'A') && (strChar <= 'Z')) || ((strChar >= 'a') && (strChar <= 'z')); + if (!isLetter) { - // e.g. ('-' | 0x20) == ('\r' | 0x20) which is not correct. - canBeLowercased = false; + allAreLetters = false; } strAsUlong |= (strChar << 16UL * i); } if (ignoreCase) { - if (!canBeLowercased) + if (!allAreLetters) { // TODO: Implement logic from UInt64OrdinalIgnoreCaseAscii // @@ -4749,7 +4747,7 @@ GenTree* Compiler::impUnrollSpanComparisonWithStrCon(GenTree* span, // return nullptr; } - strAsUlong |= 0x0020002000200020UL; + strAsUlong |= 0x0020002000200020ULL; } // We're going to emit the following tree: @@ -4786,12 +4784,14 @@ GenTree* Compiler::impUnrollSpanComparisonWithStrCon(GenTree* span, GenTree* spanData = gtNewFieldRef(TYP_BYREF, pointerHnd, spanRefClone, pointerOffset); GenTree* spanDataIndir = gtNewIndir(cmpType, spanData); - GenTreeIntCon* constStrAsIntCon = gtNewIconNode(strAsUlong, cmpType); + 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(0x0020002000200020UL, cmpType)); + spanDataIndir = gtNewOperNode(GT_OR, cmpType, spanDataIndir, gtNewIconNode(lowerBitMask, cmpType)); } // TODO: for length == 3 (not supported yet) we need to do two indir cmp ops: From e108914e2c564733b44e4a37b434eeffe7b90fe5 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 26 Dec 2020 18:18:15 +0300 Subject: [PATCH 15/15] Clean up --- src/coreclr/jit/importer.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index fd4f667e6f46e8..c7ec417fa6f237 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4343,12 +4343,8 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, assert((ni == NI_System_MemoryExtensions_Equals) || (ni == NI_System_MemoryExtensions_StartsWith)); // See StringComparison.cs - const int CurrentCulture = 0; - const int CurrentCultureIgnoreCase = 1; - const int InvariantCulture = 2; - const int InvariantCultureIgnoreCase = 3; - const int Ordinal = 4; - const int OrdinalIgnoreCase = 5; + const int Ordinal = 4; + const int OrdinalIgnoreCase = 5; // Fetch mode from the last argument. int mode = (int)impStackTop(0).val->AsIntCon()->IconValue(); @@ -4716,13 +4712,13 @@ GenTree* Compiler::impUnrollSpanComparisonWithStrCon(GenTree* span, return nullptr; } - bool allAreLetters = true; - UINT64 strAsUlong = 0; + bool allAreLetters = true; + UINT64 strAsUlong = 0; for (int i = 0; i < strLen; i++) { UINT64 strChar = str[i]; - if (strChar > '\x007f') + if (strChar > 127) { // str is not ASCII - bail out. return nullptr; @@ -4789,7 +4785,7 @@ GenTree* Compiler::impUnrollSpanComparisonWithStrCon(GenTree* span, if (ignoreCase) { ssize_t lowerBitMask = genTrimUnsignedValue(cmpType, 0x0020002000200020ULL); - + // Set "is lower" bits in all chars spanDataIndir = gtNewOperNode(GT_OR, cmpType, spanDataIndir, gtNewIconNode(lowerBitMask, cmpType)); }