diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 8f2d8059cc4cab..309b7e1c78b110 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -4504,11 +4504,12 @@ inline static bool StructHasNoPromotionFlagSet(DWORD attribs) return ((attribs & CORINFO_FLG_DONT_PROMOTE) != 0); } -/***************************************************************************** - * This node should not be referenced by anyone now. Set its values to garbage - * to catch extra references - */ - +//------------------------------------------------------------------------------ +// DEBUG_DESTROY_NODE: sets value of tree to garbage to catch extra references +// +// Arguments: +// tree: This node should not be referenced by anyone now +// inline void DEBUG_DESTROY_NODE(GenTree* tree) { #ifdef DEBUG @@ -4529,6 +4530,19 @@ inline void DEBUG_DESTROY_NODE(GenTree* tree) #endif } +//------------------------------------------------------------------------------ +// DEBUG_DESTROY_NODE: sets value of trees to garbage to catch extra references +// +// Arguments: +// tree, ...rest: These nodes should not be referenced by anyone now +// +template +void DEBUG_DESTROY_NODE(GenTree* tree, T... rest) +{ + DEBUG_DESTROY_NODE(tree); + DEBUG_DESTROY_NODE(rest...); +} + //------------------------------------------------------------------------------ // lvRefCnt: access reference count for this local var // diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index 73695932cb5439..d7949f08612e87 100644 --- a/src/coreclr/jit/fgbasic.cpp +++ b/src/coreclr/jit/fgbasic.cpp @@ -1113,12 +1113,12 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed { ni = lookupNamedIntrinsic(methodHnd); - bool foldableIntrinsc = false; + bool foldableIntrinsic = false; if (IsMathIntrinsic(ni)) { // Most Math(F) intrinsics have single arguments - foldableIntrinsc = FgStack::IsConstantOrConstArg(pushedStack.Top(), impInlineInfo); + foldableIntrinsic = FgStack::IsConstantOrConstArg(pushedStack.Top(), impInlineInfo); } else { @@ -1131,7 +1131,7 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed case NI_System_GC_KeepAlive: { pushedStack.PushUnknown(); - foldableIntrinsc = true; + foldableIntrinsic = true; break; } @@ -1145,6 +1145,20 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed break; } + case NI_System_Runtime_CompilerServices_RuntimeHelpers_IsKnownConstant: + if (FgStack::IsConstArgument(pushedStack.Top(), impInlineInfo)) + { + compInlineResult->Note(InlineObservation::CALLEE_CONST_ARG_FEEDS_ISCONST); + } + else + { + compInlineResult->Note(InlineObservation::CALLEE_ARG_FEEDS_ISCONST); + } + // RuntimeHelpers.IsKnownConstant is always folded into a const + pushedStack.PushConstant(); + foldableIntrinsic = true; + break; + // These are foldable if the first argument is a constant case NI_System_Type_get_IsValueType: case NI_System_Type_GetTypeFromHandle: @@ -1159,10 +1173,10 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed case NI_Vector128_Create: #endif { - // Top() in order to keep it as is in case of foldableIntrinsc + // Top() in order to keep it as is in case of foldableIntrinsic if (FgStack::IsConstantOrConstArg(pushedStack.Top(), impInlineInfo)) { - foldableIntrinsc = true; + foldableIntrinsic = true; } break; } @@ -1177,7 +1191,7 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed if (FgStack::IsConstantOrConstArg(pushedStack.Top(0), impInlineInfo) && FgStack::IsConstantOrConstArg(pushedStack.Top(1), impInlineInfo)) { - foldableIntrinsc = true; + foldableIntrinsic = true; pushedStack.PushConstant(); } break; @@ -1186,31 +1200,31 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed case NI_IsSupported_True: case NI_IsSupported_False: { - foldableIntrinsc = true; + foldableIntrinsic = true; pushedStack.PushConstant(); break; } #if defined(TARGET_XARCH) && defined(FEATURE_HW_INTRINSICS) case NI_Vector128_get_Count: case NI_Vector256_get_Count: - foldableIntrinsc = true; + foldableIntrinsic = true; pushedStack.PushConstant(); // TODO: check if it's a loop condition - we unroll such loops. break; case NI_Vector256_get_Zero: case NI_Vector256_get_AllBitsSet: - foldableIntrinsc = true; + foldableIntrinsic = true; pushedStack.PushUnknown(); break; #elif defined(TARGET_ARM64) && defined(FEATURE_HW_INTRINSICS) case NI_Vector64_get_Count: case NI_Vector128_get_Count: - foldableIntrinsc = true; + foldableIntrinsic = true; pushedStack.PushConstant(); break; case NI_Vector128_get_Zero: case NI_Vector128_get_AllBitsSet: - foldableIntrinsc = true; + foldableIntrinsic = true; pushedStack.PushUnknown(); break; #endif @@ -1222,7 +1236,7 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed } } - if (foldableIntrinsc) + if (foldableIntrinsic) { compInlineResult->Note(InlineObservation::CALLSITE_FOLDABLE_INTRINSIC); handled = true; diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 70000ed09ca12b..2615132e10f931 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -10827,6 +10827,9 @@ void Compiler::gtDispTree(GenTree* tree, case NI_System_Object_GetType: printf(" objGetType"); break; + case NI_System_Runtime_CompilerServices_RuntimeHelpers_IsKnownConstant: + printf(" isKnownConst"); + break; default: unreached(); diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 33e5443c1eee4a..df3066d5bc94a6 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -3879,19 +3879,25 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, return new (this, GT_LABEL) GenTree(GT_LABEL, TYP_I_IMPL); } - if (((ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_CreateSpan) || - (ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_InitializeArray)) && - IsTargetAbi(CORINFO_CORERT_ABI)) + switch (ni) { // CreateSpan must be expanded for NativeAOT - mustExpand = true; - } + case NI_System_Runtime_CompilerServices_RuntimeHelpers_CreateSpan: + case NI_System_Runtime_CompilerServices_RuntimeHelpers_InitializeArray: + mustExpand |= IsTargetAbi(CORINFO_CORERT_ABI); + break; - if ((ni == NI_System_ByReference_ctor) || (ni == NI_System_ByReference_get_Value) || - (ni == NI_System_Activator_AllocatorOf) || (ni == NI_System_Activator_DefaultConstructorOf) || - (ni == NI_System_Object_MethodTableOf) || (ni == NI_System_EETypePtr_EETypePtrOf)) - { - mustExpand = true; + case NI_System_ByReference_ctor: + case NI_System_ByReference_get_Value: + case NI_System_Activator_AllocatorOf: + case NI_System_Activator_DefaultConstructorOf: + case NI_System_Object_MethodTableOf: + case NI_System_EETypePtr_EETypePtrOf: + mustExpand = true; + break; + + default: + break; } GenTree* retNode = nullptr; @@ -3935,29 +3941,19 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, case NI_System_String_get_Length: { GenTree* op1 = impPopStack().val; - if (opts.OptimizationEnabled()) + if (op1->OperIs(GT_CNS_STR)) { - if (op1->OperIs(GT_CNS_STR)) + // Optimize `ldstr + String::get_Length()` to CNS_INT + // e.g. "Hello".Length => 5 + GenTreeIntCon* iconNode = gtNewStringLiteralLength(op1->AsStrCon()); + if (iconNode != nullptr) { - // Optimize `ldstr + String::get_Length()` to CNS_INT - // e.g. "Hello".Length => 5 - GenTreeIntCon* iconNode = gtNewStringLiteralLength(op1->AsStrCon()); - if (iconNode != nullptr) - { - retNode = iconNode; - break; - } + retNode = iconNode; + break; } - GenTreeArrLen* arrLen = gtNewArrLen(TYP_INT, op1, OFFSETOF__CORINFO_String__stringLen, compCurBB); - op1 = arrLen; - } - else - { - /* Create the expression "*(str_addr + stringLengthOffset)" */ - op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, - gtNewIconNode(OFFSETOF__CORINFO_String__stringLen, TYP_I_IMPL)); - op1 = gtNewOperNode(GT_IND, TYP_INT, op1); } + GenTreeArrLen* arrLen = gtNewArrLen(TYP_INT, op1, OFFSETOF__CORINFO_String__stringLen, compCurBB); + op1 = arrLen; // Getting the length of a null string should throw op1->gtFlags |= GTF_EXCEPT; @@ -4007,6 +4003,26 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } + case NI_System_Runtime_CompilerServices_RuntimeHelpers_IsKnownConstant: + { + GenTree* op1 = impPopStack().val; + if (op1->OperIsConst()) + { + // op1 is a known constant, replace with 'true'. + retNode = gtNewIconNode(1); + JITDUMP("\nExpanding RuntimeHelpers.IsKnownConstant to true early\n"); + // We can also consider FTN_ADDR and typeof(T) here + } + else + { + // op1 is not a known constant, we'll do the expansion in morph + retNode = new (this, GT_INTRINSIC) GenTreeIntrinsic(TYP_INT, op1, ni, method); + JITDUMP("\nConverting RuntimeHelpers.IsKnownConstant to:\n"); + DISPTREE(retNode); + } + break; + } + case NI_System_Activator_AllocatorOf: case NI_System_Activator_DefaultConstructorOf: case NI_System_Object_MethodTableOf: @@ -4283,7 +4299,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, case NI_System_Threading_Thread_get_ManagedThreadId: { - if (opts.OptimizationEnabled() && impStackTop().val->OperIs(GT_RET_EXPR)) + if (impStackTop().val->OperIs(GT_RET_EXPR)) { GenTreeCall* call = impStackTop().val->AsRetExpr()->gtInlineCandidate->AsCall(); if (call->gtCallMoreFlags & GTF_CALL_M_SPECIAL_INTRINSIC) @@ -4306,7 +4322,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, case NI_System_Threading_Interlocked_Or: case NI_System_Threading_Interlocked_And: { - if (opts.OptimizationEnabled() && compOpportunisticallyDependsOn(InstructionSet_Atomics)) + if (compOpportunisticallyDependsOn(InstructionSet_Atomics)) { assert(sig->numArgs == 2); GenTree* op2 = impPopStack().val; @@ -5352,6 +5368,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) { result = NI_System_Runtime_CompilerServices_RuntimeHelpers_InitializeArray; } + else if (strcmp(methodName, "IsKnownConstant") == 0) + { + result = NI_System_Runtime_CompilerServices_RuntimeHelpers_IsKnownConstant; + } } else if (strncmp(namespaceName, "System.Runtime.Intrinsics", 25) == 0) { diff --git a/src/coreclr/jit/inline.def b/src/coreclr/jit/inline.def index a28daf4e980fdc..a519471a3caec2 100644 --- a/src/coreclr/jit/inline.def +++ b/src/coreclr/jit/inline.def @@ -70,6 +70,8 @@ INLINE_OBSERVATION(ARG_FEEDS_CONSTANT_TEST, bool, "argument feeds constant t INLINE_OBSERVATION(ARG_FEEDS_TEST, bool, "argument feeds test", INFORMATION, CALLEE) INLINE_OBSERVATION(ARG_FEEDS_CAST, int, "argument feeds castclass or isinst", INFORMATION, CALLEE) INLINE_OBSERVATION(ARG_FEEDS_RANGE_CHECK, bool, "argument feeds range check", INFORMATION, CALLEE) +INLINE_OBSERVATION(ARG_FEEDS_ISCONST, bool, "argument feeds IsKnownConstant", INFORMATION, CALLEE) +INLINE_OBSERVATION(CONST_ARG_FEEDS_ISCONST, bool, "const argument feeds IsKnownConstant", INFORMATION, CALLEE) INLINE_OBSERVATION(ARG_STRUCT, int, "arg is a struct passed by value", INFORMATION, CALLEE) INLINE_OBSERVATION(RETURNS_STRUCT, bool, "returns a struct by value", INFORMATION, CALLEE) INLINE_OBSERVATION(ARG_STRUCT_FIELD_ACCESS, int, "ldfld/stfld over arg (struct)", INFORMATION, CALLEE) diff --git a/src/coreclr/jit/inlinepolicy.cpp b/src/coreclr/jit/inlinepolicy.cpp index 1ec47378872d54..0ea9135b82c249 100644 --- a/src/coreclr/jit/inlinepolicy.cpp +++ b/src/coreclr/jit/inlinepolicy.cpp @@ -326,6 +326,14 @@ void DefaultPolicy::NoteBool(InlineObservation obs, bool value) m_ArgFeedsRangeCheck++; break; + case InlineObservation::CALLEE_CONST_ARG_FEEDS_ISCONST: + m_ConstArgFeedsIsKnownConst = true; + break; + + case InlineObservation::CALLEE_ARG_FEEDS_ISCONST: + m_ArgFeedsIsKnownConst = true; + break; + case InlineObservation::CALLEE_UNSUPPORTED_OPCODE: propagate = true; break; @@ -727,6 +735,15 @@ double DefaultPolicy::DetermineMultiplier() JITDUMP("\nInline candidate has arg that feeds range check. Multiplier increased to %g.", multiplier); } + if (m_ConstArgFeedsIsKnownConst || (m_ArgFeedsIsKnownConst && m_IsPrejitRoot)) + { + // if we use RuntimeHelpers.IsKnownConstant we most likely expect our function to be always inlined + // at least in the case of constant arguments. In IsPrejitRoot we don't have callsite info so let's + // assume we have a constant here in order to avoid "baked" noinline + multiplier += 20; + JITDUMP("\nConstant argument feeds RuntimeHelpers.IsKnownConstant. Multiplier increased to %g.", multiplier); + } + if (m_ConstantArgFeedsConstantTest > 0) { multiplier += 3.0; @@ -1371,7 +1388,7 @@ void ExtendedDefaultPolicy::NoteInt(InlineObservation obs, int value) { SetNever(InlineObservation::CALLEE_DOES_NOT_RETURN); } - else if (!m_IsForceInline && !m_HasProfile) + else if (!m_IsForceInline && !m_HasProfile && !m_ConstArgFeedsIsKnownConst && !m_ArgFeedsIsKnownConst) { unsigned bbLimit = (unsigned)JitConfig.JitExtDefaultPolicyMaxBB(); if (m_IsPrejitRoot) @@ -1381,6 +1398,7 @@ void ExtendedDefaultPolicy::NoteInt(InlineObservation obs, int value) bbLimit += 5 + m_Switch * 10; } bbLimit += m_FoldableBranch + m_FoldableSwitch * 10; + if ((unsigned)value > bbLimit) { SetNever(InlineObservation::CALLEE_TOO_MANY_BASIC_BLOCKS); @@ -2638,6 +2656,8 @@ void DiscretionaryPolicy::DumpData(FILE* file) const fprintf(file, ",%u", m_ArgFeedsConstantTest); fprintf(file, ",%u", m_MethodIsMostlyLoadStore ? 1 : 0); fprintf(file, ",%u", m_ArgFeedsRangeCheck); + fprintf(file, ",%u", m_ConstArgFeedsIsKnownConst ? 1 : 0); + fprintf(file, ",%u", m_ArgFeedsIsKnownConst ? 1 : 0); fprintf(file, ",%u", m_ConstantArgFeedsConstantTest); fprintf(file, ",%d", m_CalleeNativeSizeEstimate); fprintf(file, ",%d", m_CallsiteNativeSizeEstimate); diff --git a/src/coreclr/jit/inlinepolicy.h b/src/coreclr/jit/inlinepolicy.h index f5f200e9f5bd71..05e8539982ee88 100644 --- a/src/coreclr/jit/inlinepolicy.h +++ b/src/coreclr/jit/inlinepolicy.h @@ -110,6 +110,8 @@ class DefaultPolicy : public LegalPolicy , m_CallsiteIsInLoop(false) , m_IsNoReturn(false) , m_IsNoReturnKnown(false) + , m_ConstArgFeedsIsKnownConst(false) + , m_ArgFeedsIsKnownConst(false) { // empty } @@ -178,6 +180,8 @@ class DefaultPolicy : public LegalPolicy bool m_CallsiteIsInLoop : 1; bool m_IsNoReturn : 1; bool m_IsNoReturnKnown : 1; + bool m_ConstArgFeedsIsKnownConst : 1; + bool m_ArgFeedsIsKnownConst : 1; }; // ExtendedDefaultPolicy is a slightly more aggressive variant of diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 1ee03c84a398f0..230c77887b572f 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5198,7 +5198,7 @@ GenTree* Compiler::fgMorphArrayIndex(GenTree* tree) asIndex->Arr()->AsStrCon()->gtSconCPX, &length); if ((cnsIndex < length) && (str != nullptr)) { - GenTree* cnsCharNode = gtNewIconNode(str[cnsIndex], elemTyp); + GenTree* cnsCharNode = gtNewIconNode(str[cnsIndex], TYP_INT); INDEBUG(cnsCharNode->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED); return cnsCharNode; } @@ -13223,6 +13223,45 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) } break; + case GT_INTRINSIC: + if (tree->AsIntrinsic()->gtIntrinsicName == + NI_System_Runtime_CompilerServices_RuntimeHelpers_IsKnownConstant) + { + // Should be expanded by the time it reaches CSE phase + assert(!optValnumCSE_phase); + + JITDUMP("\nExpanding RuntimeHelpers.IsKnownConstant to "); + if (op1->OperIsConst()) + { + // We're lucky to catch a constant here while importer was not + JITDUMP("true\n"); + DEBUG_DESTROY_NODE(tree, op1); + tree = gtNewIconNode(1); + } + else + { + GenTree* op1SideEffects = nullptr; + gtExtractSideEffList(op1, &op1SideEffects, GTF_ALL_EFFECT); + if (op1SideEffects != nullptr) + { + DEBUG_DESTROY_NODE(tree); + // Keep side-effects of op1 + tree = gtNewOperNode(GT_COMMA, TYP_INT, op1SideEffects, gtNewIconNode(0)); + JITDUMP("false with side effects:\n") + DISPTREE(tree); + } + else + { + JITDUMP("false\n"); + DEBUG_DESTROY_NODE(tree, op1); + tree = gtNewIconNode(0); + } + } + INDEBUG(tree->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED); + return tree; + } + break; + default: break; } diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index 25733a41a9c9ed..7b2108d3edd057 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -78,6 +78,7 @@ enum NamedIntrinsic : unsigned short NI_System_Runtime_CompilerServices_RuntimeHelpers_CreateSpan, NI_System_Runtime_CompilerServices_RuntimeHelpers_InitializeArray, + NI_System_Runtime_CompilerServices_RuntimeHelpers_IsKnownConstant, NI_System_String_get_Chars, NI_System_String_get_Length, diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index 8187320ec97683..498055ad6a1c51 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -117,5 +117,15 @@ internal static bool IsPrimitiveType(this CorElementType et) /// This method is intended for compiler use rather than use directly in code. T must be one of byte, sbyte, char, short, ushort, int, long, ulong, float, or double. [Intrinsic] public static unsafe ReadOnlySpan CreateSpan(RuntimeFieldHandle fldHandle) => new ReadOnlySpan(GetSpanDataFrom(fldHandle, typeof(T).TypeHandle, out int length), length); + + + // The following intrinsics return true if input is a compile-time constant + // Feel free to add more overloads on demand + + [Intrinsic] + internal static bool IsKnownConstant(string? t) => false; + + [Intrinsic] + internal static bool IsKnownConstant(char t) => false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index 9d068b4da0bc92..6a059351f376ac 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -683,6 +683,18 @@ public bool Equals([NotNullWhen(true)] string? value, StringComparison compariso // Determines whether two Strings match. public static bool Equals(string? a, string? b) { + // Transform 'str == ""' to 'str != null && str.Length == 0' if either a or b are jit-time + // constants. Otherwise, these two blocks are eliminated + if (RuntimeHelpers.IsKnownConstant(a) && a != null && a.Length == 0) + { + return b != null && b.Length == 0; + } + + if (RuntimeHelpers.IsKnownConstant(b) && b != null && b.Length == 0) + { + return a != null && a.Length == 0; + } + if (object.ReferenceEquals(a, b)) { return true; @@ -1013,7 +1025,14 @@ public bool StartsWith(string value, bool ignoreCase, CultureInfo? culture) return referenceCulture.CompareInfo.IsPrefix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); } - public bool StartsWith(char value) => Length != 0 && _firstChar == value; + public bool StartsWith(char value) + { + if (RuntimeHelpers.IsKnownConstant(value) && value != '\0') + { + return _firstChar == value; + } + return Length != 0 && _firstChar == value; + } internal static void CheckStringComparison(StringComparison comparisonType) { diff --git a/src/tests/JIT/opt/IsKnownConstant/StringEquals.cs b/src/tests/JIT/opt/IsKnownConstant/StringEquals.cs new file mode 100644 index 00000000000000..4a109ae5d65483 --- /dev/null +++ b/src/tests/JIT/opt/IsKnownConstant/StringEquals.cs @@ -0,0 +1,99 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; + +public class Program +{ + public static int Main() + { + AssertEquals(true, TestEquals1("")); + AssertEquals(false, TestEquals1(null)); + AssertEquals(false, TestEquals1("1")); + + AssertEquals(true, TestEquals2("")); + AssertEquals(false, TestEquals2(null)); + AssertEquals(false, TestEquals2("1")); + + AssertEquals(true, TestEquals3("")); + AssertEquals(false, TestEquals3(null)); + AssertEquals(false, TestEquals3("1")); + + AssertEquals(true, TestEquals4("")); + AssertEquals(false, TestEquals4(null)); + AssertEquals(false, TestEquals4("1")); + + AssertEquals(true, TestEquals5("")); + AssertEquals(false, TestEquals5(null)); + AssertEquals(false, TestEquals5("1")); + + AssertEquals(false, TestEquals6("")); + AssertEquals(true, TestEquals6(null)); + AssertEquals(false, TestEquals6("1")); + + AssertEquals(false, TestEquals7()); + AssertEquals(false, TestEquals8()); + + AssertEquals(true, TestEquals5("")); + AssertEquals(false, TestEquals5(null)); + AssertEquals(false, TestEquals5("1")); + + AssertEquals(true, TestStartWith("c")); + AssertEquals(false, TestStartWith("C")); + AssertThrowsNRE(() => TestStartWith(null)); + + return 100; + } + + private static void AssertEquals(bool expected, bool actual, [CallerLineNumber]int l = 0) + { + if (expected != actual) + throw new InvalidOperationException(); + } + + private static void AssertThrowsNRE(Action a) + { + try + { + a(); + } + catch (NullReferenceException) + { + return; + } + throw new InvalidOperationException(); + } + + private static string NullStr() => null; + private static string EmptyStr() => ""; + private static string NonEmptyStr() => "1"; + + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool TestEquals1(string str) => str == ""; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool TestEquals2(string str) => str == string.Empty; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool TestEquals3(string str) => "" == str; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool TestEquals4(string str) => string.Empty == str; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool TestEquals5(string str) => string.Empty == str; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool TestEquals6(string str) => string.Equals(NullStr(), str); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool TestEquals7() => string.Equals(NullStr(), EmptyStr()); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool TestEquals8() => string.Equals(NullStr(), NonEmptyStr()); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool TestStartWith(string str) => str.StartsWith('c'); +} diff --git a/src/tests/JIT/opt/IsKnownConstant/StringEquals.csproj b/src/tests/JIT/opt/IsKnownConstant/StringEquals.csproj new file mode 100644 index 00000000000000..6946bed81bfd5b --- /dev/null +++ b/src/tests/JIT/opt/IsKnownConstant/StringEquals.csproj @@ -0,0 +1,9 @@ + + + Exe + True + + + + +