diff options
28 files changed, 748 insertions, 63 deletions
diff --git a/src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h b/src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h index 4ce775d177..29baaa8a39 100644 --- a/src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h +++ b/src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h @@ -119,6 +119,15 @@ CORINFO_METHOD_HANDLE resolveVirtualMethod(CORINFO_METHOD_HANDLE virtualMethod, CORINFO_CLASS_HANDLE implementingClass, CORINFO_CONTEXT_HANDLE ownerType); +// Given T, return the type of the default EqualityComparer<T>. +// Returns null if the type can't be determined exactly. +CORINFO_CLASS_HANDLE getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE elemType); + +// Given resolved token that corresponds to an intrinsic classified as +// a CORINFO_INTRINSIC_GetRawHandle intrinsic, fetch the handle associated +// with the token. If this is not possible at compile-time (because the current method's +// code is shared and the token contains generic parameters) then indicate +// how the handle should be looked up at runtime. void expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult); @@ -126,8 +135,7 @@ void expandRawHandleIntrinsic( // If a method's attributes have (getMethodAttribs) CORINFO_FLG_INTRINSIC set, // getIntrinsicID() returns the intrinsic ID. // *pMustExpand tells whether or not JIT must expand the intrinsic. -CorInfoIntrinsics getIntrinsicID(CORINFO_METHOD_HANDLE method, bool* pMustExpand = NULL /* OUT */ - ); +CorInfoIntrinsics getIntrinsicID(CORINFO_METHOD_HANDLE method, bool* pMustExpand = NULL /* OUT */); // Is the given module the System.Numerics.Vectors module? // This defaults to false. diff --git a/src/ToolBox/superpmi/superpmi-shared/lwmlist.h b/src/ToolBox/superpmi/superpmi-shared/lwmlist.h index 898641c790..21f7906510 100644 --- a/src/ToolBox/superpmi/superpmi-shared/lwmlist.h +++ b/src/ToolBox/superpmi/superpmi-shared/lwmlist.h @@ -69,6 +69,7 @@ LWM(GetClassName, DWORDLONG, DWORD) LWM(GetClassNumInstanceFields, DWORDLONG, DWORD) LWM(GetClassSize, DWORDLONG, DWORD) LWM(GetCookieForPInvokeCalliSig, GetCookieForPInvokeCalliSigValue, DLDL) +LWM(GetDefaultEqualityComparerClass, DWORDLONG, DWORDLONG) LWM(GetDelegateCtor, Agnostic_GetDelegateCtorIn, Agnostic_GetDelegateCtorOut) LWM(GetEEInfo, DWORD, Agnostic_CORINFO_EE_INFO) LWM(GetEHinfo, DLD, Agnostic_CORINFO_EH_CLAUSE) diff --git a/src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp b/src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp index 02a940d2b5..0bf288d196 100644 --- a/src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp @@ -3012,6 +3012,23 @@ CORINFO_METHOD_HANDLE MethodContext::repResolveVirtualMethod(CORINFO_METHOD_HAND return (CORINFO_METHOD_HANDLE)result; } +void MethodContext::recGetDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE result) +{ + if (GetDefaultEqualityComparerClass == nullptr) + GetDefaultEqualityComparerClass = new LightWeightMap<DWORDLONG, DWORDLONG>(); + + GetDefaultEqualityComparerClass->Add((DWORDLONG)cls, (DWORDLONG)result); +} +void MethodContext::dmpGetDefaultEqualityComparerClass(DWORDLONG key, DWORDLONG value) +{ + printf("GetDefaultEqualityComparerClass key cls-%016llX, value cls-%016llX", key, value); +} +CORINFO_CLASS_HANDLE MethodContext::repGetDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls) +{ + CORINFO_CLASS_HANDLE result = (CORINFO_CLASS_HANDLE)GetDefaultEqualityComparerClass->Get((DWORDLONG)cls); + return result; +} + void MethodContext::recGetTokenTypeAsHandle(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CLASS_HANDLE result) { if (GetTokenTypeAsHandle == nullptr) diff --git a/src/ToolBox/superpmi/superpmi-shared/methodcontext.h b/src/ToolBox/superpmi/superpmi-shared/methodcontext.h index 7f79b2b9f6..52515733d3 100644 --- a/src/ToolBox/superpmi/superpmi-shared/methodcontext.h +++ b/src/ToolBox/superpmi/superpmi-shared/methodcontext.h @@ -872,6 +872,11 @@ public: CORINFO_CLASS_HANDLE implClass, CORINFO_CONTEXT_HANDLE ownerType); + void recGetDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE result); + void dmpGetDefaultEqualityComparerClass(DWORDLONG key, DWORDLONG value); + CORINFO_CLASS_HANDLE repGetDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls); + + void recGetTokenTypeAsHandle(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CLASS_HANDLE result); void dmpGetTokenTypeAsHandle(const GetTokenTypeAsHandleValue& key, DWORDLONG value); CORINFO_CLASS_HANDLE repGetTokenTypeAsHandle(CORINFO_RESOLVED_TOKEN* pResolvedToken); @@ -1252,7 +1257,7 @@ private: }; // ********************* Please keep this up-to-date to ease adding more *************** -// Highest packet number: 161 +// Highest packet number: 162 // ************************************************************************************* enum mcPackets { @@ -1319,6 +1324,7 @@ enum mcPackets Packet_GetIntConfigValue = 151, // Added 2/12/2015 Packet_GetStringConfigValue = 152, // Added 2/12/2015 Packet_GetCookieForPInvokeCalliSig = 48, + Packet_GetDefaultEqualityComparerClass = 162, // Added 9/24/2017 Packet_GetDelegateCtor = 49, Packet_GetEEInfo = 50, Packet_GetEHinfo = 51, diff --git a/src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 2a477a07e3..6051de8840 100644 --- a/src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -236,6 +236,16 @@ CORINFO_METHOD_HANDLE interceptor_ICJI::resolveVirtualMethod(CORINFO_METHOD_HAND return result; } +// Given T, return the type of the default EqualityComparer<T>. +// Returns null if the type can't be determined exactly. +CORINFO_CLASS_HANDLE interceptor_ICJI::getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls) +{ + mc->cr->AddCall("getDefaultEqualityComparerClass"); + CORINFO_CLASS_HANDLE result = original_ICorJitInfo->getDefaultEqualityComparerClass(cls); + mc->recGetDefaultEqualityComparerClass(cls, result); + return result; +} + void interceptor_ICJI::expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult) diff --git a/src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp b/src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp index a54ead5661..17da100dac 100644 --- a/src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp +++ b/src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp @@ -163,6 +163,14 @@ CORINFO_METHOD_HANDLE interceptor_ICJI::resolveVirtualMethod(CORINFO_METHOD_HAND return original_ICorJitInfo->resolveVirtualMethod(virtualMethod, implementingClass, ownerType); } +// Given T, return the type of the default EqualityComparer<T>. +// Returns null if the type can't be determined exactly. +CORINFO_CLASS_HANDLE interceptor_ICJI::getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls) +{ + mcs->AddCall("getDefaultEqualityComparerClass"); + return original_ICorJitInfo->getDefaultEqualityComparerClass(cls); +} + void interceptor_ICJI::expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult) diff --git a/src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp b/src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp index 7e0ded9f0f..df4cfcfbcd 100644 --- a/src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp +++ b/src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp @@ -150,6 +150,13 @@ CORINFO_METHOD_HANDLE interceptor_ICJI::resolveVirtualMethod(CORINFO_METHOD_HAND return original_ICorJitInfo->resolveVirtualMethod(virtualMethod, implementingClass, ownerType); } +// Given T, return the type of the default EqualityComparer<T>. +// Returns null if the type can't be determined exactly. +CORINFO_CLASS_HANDLE interceptor_ICJI::getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls) +{ + return original_ICorJitInfo->getDefaultEqualityComparerClass(cls); +} + void interceptor_ICJI::expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult) diff --git a/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp b/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp index 2e78113991..7c119b86dd 100644 --- a/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp +++ b/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp @@ -185,6 +185,15 @@ CORINFO_METHOD_HANDLE MyICJI::resolveVirtualMethod(CORINFO_METHOD_HANDLE virtua return result; } +// Given T, return the type of the default EqualityComparer<T>. +// Returns null if the type can't be determined exactly. +CORINFO_CLASS_HANDLE MyICJI::getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls) +{ + jitInstance->mc->cr->AddCall("getDefaultEqualityComparerClass"); + CORINFO_CLASS_HANDLE result = jitInstance->mc->repGetDefaultEqualityComparerClass(cls); + return result; +} + void MyICJI::expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult) diff --git a/src/inc/corinfo.h b/src/inc/corinfo.h index 3171d6b00d..aa024e0547 100644 --- a/src/inc/corinfo.h +++ b/src/inc/corinfo.h @@ -213,11 +213,11 @@ TODO: Talk about initializing strutures before use #define SELECTANY extern __declspec(selectany) #endif -SELECTANY const GUID JITEEVersionIdentifier = { /* 7f70c266-eada-427b-be8a-be1260e34b1b */ - 0x7f70c266, - 0xeada, - 0x427b, - {0xbe, 0x8a, 0xbe, 0x12, 0x60, 0xe3, 0x4b, 0x1b} +SELECTANY const GUID JITEEVersionIdentifier = { /* 76a743cd-8a07-471e-9ac4-cd5806a8ffac */ + 0x76a743cd, + 0x8a07, + 0x471e, + {0x9a, 0xc4, 0xcd, 0x58, 0x06, 0xa8, 0xff, 0xac} }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2085,6 +2085,12 @@ public: CORINFO_CONTEXT_HANDLE ownerType = NULL /* IN */ ) = 0; + // Given T, return the type of the default EqualityComparer<T>. + // Returns null if the type can't be determined exactly. + virtual CORINFO_CLASS_HANDLE getDefaultEqualityComparerClass( + CORINFO_CLASS_HANDLE elemType + ) = 0; + // Given resolved token that corresponds to an intrinsic classified as // a CORINFO_INTRINSIC_GetRawHandle intrinsic, fetch the handle associated // with the token. If this is not possible at compile-time (because the current method's diff --git a/src/jit/ICorJitInfo_API_names.h b/src/jit/ICorJitInfo_API_names.h index 34a8bc0626..fe86ea2e06 100644 --- a/src/jit/ICorJitInfo_API_names.h +++ b/src/jit/ICorJitInfo_API_names.h @@ -168,5 +168,6 @@ DEF_CLR_API(getModuleNativeEntryPointRange) DEF_CLR_API(getExpectedTargetArchitecture) DEF_CLR_API(resolveVirtualMethod) DEF_CLR_API(expandRawHandleIntrinsic) +DEF_CLR_API(getDefaultEqualityComparerClass) #undef DEF_CLR_API diff --git a/src/jit/ICorJitInfo_API_wrapper.hpp b/src/jit/ICorJitInfo_API_wrapper.hpp index 78177e16d7..69311a5917 100644 --- a/src/jit/ICorJitInfo_API_wrapper.hpp +++ b/src/jit/ICorJitInfo_API_wrapper.hpp @@ -1620,6 +1620,16 @@ CORINFO_METHOD_HANDLE WrapICorJitInfo::resolveVirtualMethod( return result; } +CORINFO_CLASS_HANDLE WrapICorJitInfo::getDefaultEqualityComparerClass( + CORINFO_CLASS_HANDLE elemType) +{ + API_ENTER(getDefaultEqualityComparerClass); + CORINFO_CLASS_HANDLE result = wrapHnd->getDefaultEqualityComparerClass(elemType); + API_LEAVE(getDefaultEqualityComparerClass); + return result; +} + + void WrapICorJitInfo::expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult) diff --git a/src/jit/compiler.h b/src/jit/compiler.h index 1dcc76338c..4e322e0e4a 100644 --- a/src/jit/compiler.h +++ b/src/jit/compiler.h @@ -2966,12 +2966,13 @@ protected: IL_OFFSET rawILOffset); void impDevirtualizeCall(GenTreeCall* call, - GenTreePtr thisObj, CORINFO_METHOD_HANDLE* method, unsigned* methodFlags, CORINFO_CONTEXT_HANDLE* contextHandle, CORINFO_CONTEXT_HANDLE* exactContextHandle); + CORINFO_CLASS_HANDLE impGetSpecialIntrinsicExactReturnType(CORINFO_METHOD_HANDLE specialIntrinsicHandle); + bool impMethodInfo_hasRetBuffArg(CORINFO_METHOD_INFO* methInfo); GenTreePtr impFixupCallStructReturn(GenTreeCall* call, CORINFO_CLASS_HANDLE retClsHnd); diff --git a/src/jit/flowgraph.cpp b/src/jit/flowgraph.cpp index ff3269322b..355a0c5610 100644 --- a/src/jit/flowgraph.cpp +++ b/src/jit/flowgraph.cpp @@ -7056,6 +7056,19 @@ GenTreeCall* Compiler::fgGetStaticsCCtorHelper(CORINFO_CLASS_HANDLE cls, CorInfo GenTreeCall* result = gtNewHelperCallNode(helper, type, argList); result->gtFlags |= callFlags; + // If we're importing the special EqualityComparer<T>.Default + // intrinsic, flag the helper call. Later during inlining, we can + // remove the helper call if the associated field lookup is unused. + if ((info.compFlags & CORINFO_FLG_JIT_INTRINSIC) != 0) + { + NamedIntrinsic ni = lookupNamedIntrinsic(info.compMethodHnd); + if (ni == NI_System_Collections_Generic_EqualityComparer_get_Default) + { + JITDUMP("\nmarking helper call [06%u] as special dce...\n", result->gtTreeID); + result->gtCallMoreFlags |= GTF_CALL_M_HELPER_SPECIAL_DCE; + } + } + return result; } @@ -22233,7 +22246,7 @@ Compiler::fgWalkResult Compiler::fgUpdateInlineReturnExpressionPlaceHolder(GenTr CORINFO_METHOD_HANDLE method = call->gtCallMethHnd; unsigned methodFlags = 0; CORINFO_CONTEXT_HANDLE context = nullptr; - comp->impDevirtualizeCall(call, tree, &method, &methodFlags, &context, nullptr); + comp->impDevirtualizeCall(call, &method, &methodFlags, &context, nullptr); } } } @@ -22928,8 +22941,8 @@ GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) GenTreeStmt* callStmt = inlineInfo->iciStmt; IL_OFFSETX callILOffset = callStmt->gtStmtILoffsx; GenTreeStmt* postStmt = callStmt->gtNextStmt; - GenTreePtr afterStmt = callStmt; // afterStmt is the place where the new statements should be inserted after. - GenTreePtr newStmt = nullptr; + GenTree* afterStmt = callStmt; // afterStmt is the place where the new statements should be inserted after. + GenTree* newStmt = nullptr; GenTreeCall* call = inlineInfo->iciCall->AsCall(); noway_assert(call->gtOper == GT_CALL); @@ -23074,6 +23087,8 @@ GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) if (argInfo.argHasSideEff) { noway_assert(argInfo.argIsUsed == false); + newStmt = nullptr; + bool append = true; if (argNode->gtOper == GT_OBJ || argNode->gtOper == GT_MKREFANY) { @@ -23084,16 +23099,92 @@ GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) } else { - newStmt = gtNewStmt(gtUnusedValNode(argNode), callILOffset); + // In some special cases, unused args with side effects can + // trigger further changes. + // + // (1) If the arg is a static field access and the field access + // was produced by a call to EqualityComparer<T>.get_Default, the + // helper call to ensure the field has a value can be suppressed. + // This helper call is marked as a "Special DCE" helper during + // importation, over in fgGetStaticsCCtorHelper. + // + // (2) NYI. If, after tunneling through GT_RET_VALs, we find that + // the actual arg expression has no side effects, we can skip + // appending all together. This will help jit TP a bit. + // + // Chase through any GT_RET_EXPRs to find the actual argument + // expression. + GenTree* actualArgNode = argNode; + while (actualArgNode->gtOper == GT_RET_EXPR) + { + actualArgNode = actualArgNode->gtRetExpr.gtInlineCandidate; + } + + // For case (1) + // + // Look for the following tree shapes + // prejit: (IND (ADD (CONST, CALL(special dce helper...)))) + // jit : (COMMA (CALL(special dce helper...), (FIELD ...))) + if (actualArgNode->gtOper == GT_COMMA) + { + // Look for (COMMA (CALL(special dce helper...), (FIELD ...))) + GenTree* op1 = actualArgNode->gtOp.gtOp1; + GenTree* op2 = actualArgNode->gtOp.gtOp2; + if (op1->IsCall() && ((op1->gtCall.gtCallMoreFlags & GTF_CALL_M_HELPER_SPECIAL_DCE) != 0) && + (op2->gtOper == GT_FIELD) && ((op2->gtFlags & GTF_EXCEPT) == 0)) + { + JITDUMP("\nPerforming special dce on unused arg [%06u]:" + " actual arg [%06u] helper call [%06u]\n", + argNode->gtTreeID, actualArgNode->gtTreeID, op1->gtTreeID); + // Drop the whole tree + append = false; + } + } + else if (actualArgNode->gtOper == GT_IND) + { + // Look for (IND (ADD (CONST, CALL(special dce helper...)))) + GenTree* addr = actualArgNode->gtOp.gtOp1; + + if (addr->gtOper == GT_ADD) + { + GenTree* op1 = addr->gtOp.gtOp1; + GenTree* op2 = addr->gtOp.gtOp2; + if (op1->IsCall() && + ((op1->gtCall.gtCallMoreFlags & GTF_CALL_M_HELPER_SPECIAL_DCE) != 0) && + op2->IsCnsIntOrI()) + { + // Drop the whole tree + JITDUMP("\nPerforming special dce on unused arg [%06u]:" + " actual arg [%06u] helper call [%06u]\n", + argNode->gtTreeID, actualArgNode->gtTreeID, op1->gtTreeID); + append = false; + } + } + } } - afterStmt = fgInsertStmtAfter(block, afterStmt, newStmt); -#ifdef DEBUG - if (verbose) + if (!append) { - gtDispTree(afterStmt); + assert(newStmt == nullptr); + JITDUMP("Arg tree side effects were discardable, not appending anything for arg\n"); } + else + { + // If we don't have something custom to append, + // just append the arg node as an unused value. + if (newStmt == nullptr) + { + newStmt = gtNewStmt(gtUnusedValNode(argNode), callILOffset); + } + + afterStmt = fgInsertStmtAfter(block, afterStmt, newStmt); +#ifdef DEBUG + if (verbose) + { + gtDispTree(afterStmt); + } #endif // DEBUG + } } else if (argNode->IsBoxedValue()) { diff --git a/src/jit/gentree.cpp b/src/jit/gentree.cpp index 965b5243ee..7e06cdbd4f 100644 --- a/src/jit/gentree.cpp +++ b/src/jit/gentree.cpp @@ -6415,6 +6415,7 @@ GenTreeLclFld* Compiler::gtNewLclFldNode(unsigned lnum, var_types type, unsigned } GenTreePtr Compiler::gtNewInlineCandidateReturnExpr(GenTreePtr inlineCandidate, var_types type) + { assert(GenTree::s_gtNodeSizes[GT_RET_EXPR] == TREE_NODE_SZ_LARGE); diff --git a/src/jit/gentree.h b/src/jit/gentree.h index 62bd8cbb08..4361d16af3 100644 --- a/src/jit/gentree.h +++ b/src/jit/gentree.h @@ -3654,6 +3654,8 @@ struct GenTreeCall final : public GenTree // to restore real function address and load hidden argument // as the first argument for calli. It is CoreRT replacement for instantiating // stubs, because executable code cannot be generated at runtime. +#define GTF_CALL_M_HELPER_SPECIAL_DCE 0x00020000 // GT_CALL -- this helper call can be removed if it is part of a comma and + // the comma result is unused. // clang-format on @@ -4997,7 +4999,7 @@ protected: struct GenTreeRetExpr : public GenTree { - GenTreePtr gtInlineCandidate; + GenTree* gtInlineCandidate; CORINFO_CLASS_HANDLE gtRetClsHnd; diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp index d41cafcd17..fcf3ddc1ef 100644 --- a/src/jit/importer.cpp +++ b/src/jit/importer.cpp @@ -3738,7 +3738,7 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis, switch (ni) { - case NI_Enum_HasFlag: + case NI_System_Enum_HasFlag: { GenTree* thisOp = impStackTop(1).val; GenTree* flagOp = impStackTop(0).val; @@ -3772,6 +3772,13 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis, break; } + case NI_System_Collections_Generic_EqualityComparer_get_Default: + { + // Flag for later handling during devirtualization. + isSpecial = true; + break; + } + default: break; } @@ -3923,25 +3930,31 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) const char* namespaceName = nullptr; const char* methodName = info.compCompHnd->getMethodNameFromMetadata(method, &className, &namespaceName); - // Currently we only have intrinsics at the method level, so we can check that - // namespaceName, className, and methodName are all not null upfront. + if ((namespaceName == nullptr) || (className == nullptr) || (methodName == nullptr)) + { + return result; + } - if ((namespaceName != nullptr) && (className != nullptr) && (methodName != nullptr)) + if (strcmp(namespaceName, "System") == 0) { - if (strcmp(namespaceName, "System") == 0) + if ((strcmp(className, "Enum") == 0) && (strcmp(methodName, "HasFlag") == 0)) { - if ((strcmp(className, "Enum") == 0) && (strcmp(methodName, "HasFlag") == 0)) - { - result = NI_Enum_HasFlag; - } - else if ((strcmp(className, "MathF") == 0) && (strcmp(methodName, "Round") == 0)) - { - result = NI_MathF_Round; - } - else if ((strcmp(className, "Math") == 0) && (strcmp(methodName, "Round") == 0)) - { - result = NI_Math_Round; - } + result = NI_System_Enum_HasFlag; + } + else if ((strcmp(className, "MathF") == 0) && (strcmp(methodName, "Round") == 0)) + { + result = NI_MathF_Round; + } + else if ((strcmp(className, "Math") == 0) && (strcmp(methodName, "Round") == 0)) + { + result = NI_Math_Round; + } + } + else if (strcmp(namespaceName, "System.Collections.Generic") == 0) + { + if ((strcmp(className, "EqualityComparer`1") == 0) && (strcmp(methodName, "get_Default") == 0)) + { + result = NI_System_Collections_Generic_EqualityComparer_get_Default; } } @@ -7742,29 +7755,25 @@ var_types Compiler::impImportCall(OPCODE opcode, } } - /* Is this a virtual or interface call? */ + // Store the "this" value in the call + call->gtFlags |= obj->gtFlags & GTF_GLOB_EFFECT; + call->gtCall.gtCallObjp = obj; + // Is this a virtual or interface call? if ((call->gtFlags & GTF_CALL_VIRT_KIND_MASK) != GTF_CALL_NONVIRT) { - /* only true object pointers can be virtual */ + // only true object pointers can be virtual assert(obj->gtType == TYP_REF); // See if we can devirtualize. - impDevirtualizeCall(call->AsCall(), obj, &callInfo->hMethod, &callInfo->methodFlags, - &callInfo->contextHandle, &exactContextHnd); + impDevirtualizeCall(call->AsCall(), &callInfo->hMethod, &callInfo->methodFlags, &callInfo->contextHandle, + &exactContextHnd); } - else + + if (impIsThis(obj)) { - if (impIsThis(obj)) - { - call->gtCall.gtCallMoreFlags |= GTF_CALL_M_NONVIRT_SAME_THIS; - } + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_NONVIRT_SAME_THIS; } - - /* Store the "this" value in the call */ - - call->gtFlags |= obj->gtFlags & GTF_GLOB_EFFECT; - call->gtCall.gtCallObjp = obj; } //------------------------------------------------------------------------- @@ -17686,7 +17695,7 @@ void Compiler::impInlineRecordArgInfo(InlineInfo* pInlineInfo, inlCurArgInfo->argIsInvariant = true; if (inlCurArgInfo->argIsThis && (curArgVal->gtOper == GT_CNS_INT) && (curArgVal->gtIntCon.gtIconVal == 0)) { - /* Abort, but do not mark as not inlinable */ + // Abort inlining at this call site inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_HAS_NULL_THIS); return; } @@ -18831,9 +18840,8 @@ bool Compiler::IsMathIntrinsic(GenTreePtr tree) // // Arguments: // call -- the call node to examine/modify -// thisObj -- the value of 'this' for the call // method -- [IN/OUT] the method handle for call. Updated iff call devirtualized. -// methodAttribs -- [IN/OUT] flags for the method to call. Updated iff call devirtualized. +// methodFlags -- [IN/OUT] flags for the method to call. Updated iff call devirtualized. // contextHandle -- [IN/OUT] context handle for the call. Updated iff call devirtualized. // exactContextHnd -- [OUT] updated context handle iff call devirtualized // @@ -18855,7 +18863,6 @@ bool Compiler::IsMathIntrinsic(GenTreePtr tree) // the 'this obj' of a subsequent virtual call. // void Compiler::impDevirtualizeCall(GenTreeCall* call, - GenTreePtr thisObj, CORINFO_METHOD_HANDLE* method, unsigned* methodFlags, CORINFO_CONTEXT_HANDLE* contextHandle, @@ -18940,9 +18947,41 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, } // See what we know about the type of 'this' in the call. - bool isExact = false; - bool objIsNonNull = false; - CORINFO_CLASS_HANDLE objClass = gtGetClassHandle(thisObj, &isExact, &objIsNonNull); + GenTree* thisObj = call->gtCallObjp->gtEffectiveVal(false); + GenTree* actualThisObj = nullptr; + bool isExact = false; + bool objIsNonNull = false; + CORINFO_CLASS_HANDLE objClass = gtGetClassHandle(thisObj, &isExact, &objIsNonNull); + + // See if we have special knowlege that can get us a type or a better type. + if ((objClass == nullptr) || !isExact) + { + actualThisObj = thisObj; + + // Walk back through any return expression placeholders + while (actualThisObj->OperGet() == GT_RET_EXPR) + { + actualThisObj = actualThisObj->gtRetExpr.gtInlineCandidate; + } + + // See if we landed on a call to a special intrinsic method + if (actualThisObj->IsCall()) + { + GenTreeCall* thisObjCall = actualThisObj->AsCall(); + if ((thisObjCall->gtCallMoreFlags & GTF_CALL_M_SPECIAL_INTRINSIC) != 0) + { + assert(thisObjCall->gtCallType == CT_USER_FUNC); + CORINFO_METHOD_HANDLE specialIntrinsicHandle = thisObjCall->gtCallMethHnd; + CORINFO_CLASS_HANDLE specialObjClass = impGetSpecialIntrinsicExactReturnType(specialIntrinsicHandle); + if (specialObjClass != nullptr) + { + objClass = specialObjClass; + isExact = true; + objIsNonNull = true; + } + } + } + } // Bail if we know nothing. if (objClass == nullptr) @@ -19165,6 +19204,64 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, } //------------------------------------------------------------------------ +// impGetSpecialIntrinsicExactReturnType: Look for special cases where a call +// to an intrinsic returns an exact type +// +// Arguments: +// methodHnd -- handle for the special intrinsic method +// +// Returns: +// Exact class handle returned by the intrinsic call, if known. +// Nullptr if not known, or not likely to lead to beneficial optimization. + +CORINFO_CLASS_HANDLE Compiler::impGetSpecialIntrinsicExactReturnType(CORINFO_METHOD_HANDLE methodHnd) +{ + JITDUMP("Special intrinsic: looking for exact type returned by %s\n", eeGetMethodFullName(methodHnd)); + + CORINFO_CLASS_HANDLE result = nullptr; + + // See what intrinisc we have... + const NamedIntrinsic ni = lookupNamedIntrinsic(methodHnd); + switch (ni) + { + case NI_System_Collections_Generic_EqualityComparer_get_Default: + { + // Expect one class generic parameter; figure out which it is. + CORINFO_SIG_INFO sig; + info.compCompHnd->getMethodSig(methodHnd, &sig); + assert(sig.sigInst.classInstCount == 1); + CORINFO_CLASS_HANDLE typeHnd = sig.sigInst.classInst[0]; + assert(typeHnd != nullptr); + const DWORD typeAttribs = info.compCompHnd->getClassAttribs(typeHnd); + const bool isFinalType = ((typeAttribs & CORINFO_FLG_FINAL) != 0); + + // If we do not have a final type, devirt & inlining is + // unlikely to result in much simplification. + if (isFinalType) + { + result = info.compCompHnd->getDefaultEqualityComparerClass(typeHnd); + JITDUMP("Special intrinsic for type %s: return type is %s\n", eeGetClassName(typeHnd), + result != nullptr ? eeGetClassName(result) : "unknown"); + } + else + { + JITDUMP("Special intrinsic for type %s: type not final, so deferring opt\n", eeGetClassName(typeHnd)); + } + + break; + } + + default: + { + JITDUMP("This special intrinsic not handled, sorry...\n"); + break; + } + } + + return result; +} + +//------------------------------------------------------------------------ // impAllocateToken: create CORINFO_RESOLVED_TOKEN into jit-allocated memory and init it. // // Arguments: diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index 471fe8a663..034b6d653e 100644 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -8865,7 +8865,7 @@ NO_TAIL_CALL: // Check for a new-style jit intrinsic. const NamedIntrinsic ni = lookupNamedIntrinsic(call->gtCallMethHnd); - if (ni == NI_Enum_HasFlag) + if (ni == NI_System_Enum_HasFlag) { GenTree* thisOp = call->gtCallObjp; GenTree* flagOp = call->gtCallArgs->gtOp.gtOp1; diff --git a/src/jit/namedintrinsiclist.h b/src/jit/namedintrinsiclist.h index 2cbdb45965..fa75cc27b6 100644 --- a/src/jit/namedintrinsiclist.h +++ b/src/jit/namedintrinsiclist.h @@ -9,10 +9,11 @@ enum NamedIntrinsic { - NI_Illegal = 0, - NI_Enum_HasFlag = 1, - NI_MathF_Round = 2, - NI_Math_Round = 3 + NI_Illegal = 0, + NI_System_Enum_HasFlag = 1, + NI_MathF_Round = 2, + NI_Math_Round = 3, + NI_System_Collections_Generic_EqualityComparer_get_Default = 4 }; #endif // _NAMEDINTRINSICLIST_H_ diff --git a/src/mscorlib/src/System/Collections/Generic/ComparerHelpers.cs b/src/mscorlib/src/System/Collections/Generic/ComparerHelpers.cs index 3e428413d4..1c4b9bbc09 100644 --- a/src/mscorlib/src/System/Collections/Generic/ComparerHelpers.cs +++ b/src/mscorlib/src/System/Collections/Generic/ComparerHelpers.cs @@ -23,7 +23,8 @@ namespace System.Collections.Generic /// </summary> /// <param name="type">The type to create the default comparer for.</param> /// <remarks> - /// The logic in this method is replicated in vm/compile.cpp to ensure that NGen saves the right instantiations. + /// The logic in this method is replicated in vm/compile.cpp to ensure that NGen saves the right instantiations, + /// and in vm/jitinterface.cpp so the jit can model the behavior of this method. /// </remarks> internal static object CreateDefaultComparer(Type type) { diff --git a/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs b/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs index 2b2869ab4a..06af626526 100644 --- a/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs +++ b/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs @@ -21,7 +21,7 @@ namespace System.Collections.Generic { // To minimize generic instantiation overhead of creating the comparer per type, we keep the generic portion of the code as small // as possible and define most of the creation logic in a non-generic class. - public static EqualityComparer<T> Default { get; } = (EqualityComparer<T>)ComparerHelpers.CreateDefaultEqualityComparer(typeof(T)); + public static EqualityComparer<T> Default { [Intrinsic] get; } = (EqualityComparer<T>)ComparerHelpers.CreateDefaultEqualityComparer(typeof(T)); public abstract bool Equals(T x, T y); public abstract int GetHashCode(T obj); @@ -70,6 +70,7 @@ namespace System.Collections.Generic [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] internal class GenericEqualityComparer<T> : EqualityComparer<T> where T : IEquatable<T> { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(T x, T y) { if (x != null) @@ -81,6 +82,7 @@ namespace System.Collections.Generic return true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T obj) => obj?.GetHashCode() ?? 0; internal override int IndexOf(T[] array, T value, int startIndex, int count) @@ -137,6 +139,7 @@ namespace System.Collections.Generic [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] internal sealed class NullableEqualityComparer<T> : EqualityComparer<T?> where T : struct, IEquatable<T> { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(T? x, T? y) { if (x.HasValue) @@ -148,6 +151,7 @@ namespace System.Collections.Generic return true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T? obj) => obj.GetHashCode(); internal override int IndexOf(T?[] array, T? value, int startIndex, int count) @@ -202,6 +206,7 @@ namespace System.Collections.Generic [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] internal sealed class ObjectEqualityComparer<T> : EqualityComparer<T> { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(T x, T y) { if (x != null) @@ -213,6 +218,7 @@ namespace System.Collections.Generic return true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T obj) => obj?.GetHashCode() ?? 0; internal override int IndexOf(T[] array, T value, int startIndex, int count) @@ -297,11 +303,13 @@ namespace System.Collections.Generic [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] internal sealed class ByteEqualityComparer : EqualityComparer<byte> { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(byte x, byte y) { return x == y; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(byte b) { return b.GetHashCode(); @@ -346,6 +354,7 @@ namespace System.Collections.Generic [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] internal class EnumEqualityComparer<T> : EqualityComparer<T>, ISerializable where T : struct { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(T x, T y) { int x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast(x); @@ -353,6 +362,7 @@ namespace System.Collections.Generic return x_final == y_final; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T obj) { int x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast(obj); @@ -413,6 +423,7 @@ namespace System.Collections.Generic // This is used by the serialization engine. public SByteEnumEqualityComparer(SerializationInfo information, StreamingContext context) { } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T obj) { int x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast(obj); @@ -429,6 +440,7 @@ namespace System.Collections.Generic // This is used by the serialization engine. public ShortEnumEqualityComparer(SerializationInfo information, StreamingContext context) { } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T obj) { int x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast(obj); @@ -440,6 +452,7 @@ namespace System.Collections.Generic [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] internal sealed class LongEnumEqualityComparer<T> : EqualityComparer<T>, ISerializable where T : struct { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(T x, T y) { long x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCastLong(x); @@ -447,6 +460,7 @@ namespace System.Collections.Generic return x_final == y_final; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T obj) { long x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCastLong(obj); diff --git a/src/vm/compile.cpp b/src/vm/compile.cpp index b3d187c555..196e2163a5 100644 --- a/src/vm/compile.cpp +++ b/src/vm/compile.cpp @@ -5162,6 +5162,7 @@ static void SpecializeComparer(SString& ss, Instantiation& inst) // // This method has duplicated logic from bcl\system\collections\generic\equalitycomparer.cs +// and matching logic in jitinterface.cpp // static void SpecializeEqualityComparer(SString& ss, Instantiation& inst) { diff --git a/src/vm/jitinterface.cpp b/src/vm/jitinterface.cpp index 83e19be8a1..aa41064198 100644 --- a/src/vm/jitinterface.cpp +++ b/src/vm/jitinterface.cpp @@ -8684,6 +8684,123 @@ void CEEInfo::expandRawHandleIntrinsic( } /*********************************************************************/ +CORINFO_CLASS_HANDLE CEEInfo::getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE elemType) +{ + CONTRACTL { + SO_TOLERANT; + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + CORINFO_CLASS_HANDLE result = NULL; + + JIT_TO_EE_TRANSITION(); + + result = getDefaultEqualityComparerClassHelper(elemType); + + EE_TO_JIT_TRANSITION(); + + return result; +} + +CORINFO_CLASS_HANDLE CEEInfo::getDefaultEqualityComparerClassHelper(CORINFO_CLASS_HANDLE elemType) +{ + CONTRACTL { + SO_TOLERANT; + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + // Mirrors the logic in BCL's CompareHelpers.CreateDefaultEqualityComparer + // And in compile.cpp's SpecializeEqualityComparer + TypeHandle elemTypeHnd(elemType); + + // Special case for byte + if (elemTypeHnd.AsMethodTable()->HasSameTypeDefAs(MscorlibBinder::GetClass(CLASS__ELEMENT_TYPE_U1))) + { + return CORINFO_CLASS_HANDLE(MscorlibBinder::GetClass(CLASS__BYTE_EQUALITYCOMPARER)); + } + + // Else we'll need to find the appropriate instantation + Instantiation inst(&elemTypeHnd, 1); + + // If T implements IEquatable<T> + if (elemTypeHnd.CanCastTo(TypeHandle(MscorlibBinder::GetClass(CLASS__IEQUATABLEGENERIC)).Instantiate(inst))) + { + TypeHandle resultTh = ((TypeHandle)MscorlibBinder::GetClass(CLASS__GENERIC_EQUALITYCOMPARER)).Instantiate(inst); + return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable()); + } + + // Nullable<T> + if (Nullable::IsNullableType(elemTypeHnd)) + { + Instantiation nullableInst = elemTypeHnd.AsMethodTable()->GetInstantiation(); + TypeHandle nullableComparer = TypeHandle(MscorlibBinder::GetClass(CLASS__IEQUATABLEGENERIC)).Instantiate(nullableInst); + if (nullableInst[0].CanCastTo(nullableComparer)) + { + return CORINFO_CLASS_HANDLE(nullableComparer.GetMethodTable()); + } + } + + // Enum + // + // We need to special case the Enum comparers based on their underlying type, + // to avoid boxing and call the correct versions of GetHashCode. + if (elemTypeHnd.IsEnum()) + { + MethodTable* targetClass = NULL; + CorElementType normType = elemTypeHnd.GetVerifierCorElementType(); + + switch(normType) + { + case ELEMENT_TYPE_I1: + { + targetClass = MscorlibBinder::GetClass(CLASS__SBYTE_ENUM_EQUALITYCOMPARER); + break; + } + + case ELEMENT_TYPE_I2: + { + targetClass = MscorlibBinder::GetClass(CLASS__SHORT_ENUM_EQUALITYCOMPARER); + break; + } + + case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_U2: + case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_U4: + { + targetClass = MscorlibBinder::GetClass(CLASS__ENUM_EQUALITYCOMPARER); + break; + } + + case ELEMENT_TYPE_I8: + case ELEMENT_TYPE_U8: + { + targetClass = MscorlibBinder::GetClass(CLASS__LONG_ENUM_EQUALITYCOMPARER); + break; + } + + default: + break; + } + + if (targetClass != NULL) + { + TypeHandle resultTh = ((TypeHandle)targetClass->GetCanonicalMethodTable()).Instantiate(inst); + return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable()); + } + } + + // Default case + TypeHandle resultTh = ((TypeHandle)MscorlibBinder::GetClass(CLASS__OBJECT_EQUALITYCOMPARER)).Instantiate(inst); + + return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable()); +} + +/*********************************************************************/ void CEEInfo::getFunctionEntryPoint(CORINFO_METHOD_HANDLE ftnHnd, CORINFO_CONST_LOOKUP * pResult, CORINFO_ACCESS_FLAGS accessFlags) diff --git a/src/vm/jitinterface.h b/src/vm/jitinterface.h index 8ba65c3964..b27e160e80 100644 --- a/src/vm/jitinterface.h +++ b/src/vm/jitinterface.h @@ -743,6 +743,14 @@ public: CORINFO_CONTEXT_HANDLE ownerType ); + CORINFO_CLASS_HANDLE getDefaultEqualityComparerClass( + CORINFO_CLASS_HANDLE elemType + ); + + CORINFO_CLASS_HANDLE getDefaultEqualityComparerClassHelper( + CORINFO_CLASS_HANDLE elemType + ); + void expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult); diff --git a/src/vm/mscorlib.h b/src/vm/mscorlib.h index b86930693d..4942e0d8a9 100644 --- a/src/vm/mscorlib.h +++ b/src/vm/mscorlib.h @@ -1449,6 +1449,17 @@ DEFINE_CLASS(UTF8BUFFERMARSHALER, StubHelpers, UTF8BufferMarshaler) DEFINE_METHOD(UTF8BUFFERMARSHALER, CONVERT_TO_NATIVE, ConvertToNative, NoSig) DEFINE_METHOD(UTF8BUFFERMARSHALER, CONVERT_TO_MANAGED, ConvertToManaged, NoSig) +// Classes referenced in EqualityComparer<T>.Default optimization + +DEFINE_CLASS(BYTE_EQUALITYCOMPARER, CollectionsGeneric, ByteEqualityComparer) +DEFINE_CLASS(SHORT_ENUM_EQUALITYCOMPARER, CollectionsGeneric, ShortEnumEqualityComparer`1) +DEFINE_CLASS(SBYTE_ENUM_EQUALITYCOMPARER, CollectionsGeneric, SByteEnumEqualityComparer`1) +DEFINE_CLASS(ENUM_EQUALITYCOMPARER, CollectionsGeneric, EnumEqualityComparer`1) +DEFINE_CLASS(LONG_ENUM_EQUALITYCOMPARER, CollectionsGeneric, LongEnumEqualityComparer`1) +DEFINE_CLASS(NULLABLE_EQUALITYCOMPARER, CollectionsGeneric, NullableEqualityComparer`1) +DEFINE_CLASS(GENERIC_EQUALITYCOMPARER, CollectionsGeneric, GenericEqualityComparer`1) +DEFINE_CLASS(OBJECT_EQUALITYCOMPARER, CollectionsGeneric, ObjectEqualityComparer`1) + #undef DEFINE_CLASS #undef DEFINE_METHOD #undef DEFINE_FIELD diff --git a/src/zap/zapinfo.cpp b/src/zap/zapinfo.cpp index b8ef3eab91..01d3a3b7cc 100644 --- a/src/zap/zapinfo.cpp +++ b/src/zap/zapinfo.cpp @@ -3704,6 +3704,12 @@ CORINFO_METHOD_HANDLE ZapInfo::resolveVirtualMethod( return m_pEEJitInfo->resolveVirtualMethod(virtualMethod, implementingClass, ownerType); } +CORINFO_CLASS_HANDLE ZapInfo::getDefaultEqualityComparerClass( + CORINFO_CLASS_HANDLE elemType) +{ + return m_pEEJitInfo->getDefaultEqualityComparerClass(elemType); +} + void ZapInfo::expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult) diff --git a/src/zap/zapinfo.h b/src/zap/zapinfo.h index d533975c79..5bb86dd88a 100644 --- a/src/zap/zapinfo.h +++ b/src/zap/zapinfo.h @@ -675,6 +675,10 @@ public: CORINFO_CONTEXT_HANDLE ownerType ); + CORINFO_CLASS_HANDLE getDefaultEqualityComparerClass( + CORINFO_CLASS_HANDLE elemType + ); + void expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult); diff --git a/tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.cs b/tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.cs new file mode 100644 index 0000000000..b9da9f17fa --- /dev/null +++ b/tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.cs @@ -0,0 +1,207 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Xunit.Performance; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Xunit; + +[assembly: OptimizeForBenchmarks] + +// Performance tests for optimizations related to EqualityComparer<T>.Default + +namespace Devirtualization +{ + public class EqualityComparerFixture<T> where T : IEquatable<T> + { + IEqualityComparer<T> comparer; + + public EqualityComparerFixture(IEqualityComparer<T> customComparer = null) + { + comparer = customComparer ?? EqualityComparer<T>.Default; + } + + // Baseline method showing unoptimized performance + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public bool CompareNoOpt(ref T a, ref T b) + { + return EqualityComparer<T>.Default.Equals(a, b); + } + + // The code this method invokes should be well-optimized + [MethodImpl(MethodImplOptions.NoInlining)] + public bool Compare(ref T a, ref T b) + { + return EqualityComparer<T>.Default.Equals(a, b); + } + + // This models how Dictionary uses a comparer. We're not + // yet able to optimize such cases. + [MethodImpl(MethodImplOptions.NoInlining)] + public bool CompareCached(ref T a, ref T b) + { + return comparer.Equals(a, b); + } + + private static IEqualityComparer<T> Wrapped() + { + return EqualityComparer<T>.Default; + } + + // We would need enhancements to late devirtualization + // to optimize this case. + [MethodImpl(MethodImplOptions.NoInlining)] + public bool CompareWrapped(ref T x, ref T y) + { + return Wrapped().Equals(x, y); + } + + public bool BenchCompareNoOpt(ref T t, long count) + { + bool result = true; + for (int i = 0; i < count; i++) + { + result &= CompareNoOpt(ref t, ref t); + } + return result; + } + + public bool BenchCompare(ref T t, long count) + { + bool result = true; + for (int i = 0; i < count; i++) + { + result &= Compare(ref t, ref t); + } + return result; + } + + public bool BenchCompareCached(ref T t, long count) + { + bool result = true; + for (int i = 0; i < count; i++) + { + result &= CompareCached(ref t, ref t); + } + return result; + } + + public bool BenchCompareWrapped(ref T t, long count) + { + bool result = true; + for (int i = 0; i < count; i++) + { + result &= CompareWrapped(ref t, ref t); + } + return result; + } + } + + public class EqualityComparer + { + +#if DEBUG + public const int Iterations = 1; +#else + public const int Iterations = 150 * 1000 * 1000; +#endif + + public enum E + { + RED = 1, + BLUE = 2 + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Consume(bool b) { } + + [Benchmark(InnerIterationCount = Iterations)] + public static void ValueTupleCompareNoOpt() + { + var valueTupleFixture = new EqualityComparerFixture<ValueTuple<byte, E, int>>(); + var v0 = new ValueTuple<byte, E, int>(3, E.RED, 11); + var result = true; + + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + result &= valueTupleFixture.BenchCompareNoOpt(ref v0, Benchmark.InnerIterationCount); + } + } + + Consume(result); + } + + [Benchmark(InnerIterationCount = Iterations)] + public static void ValueTupleCompare() + { + var valueTupleFixture = new EqualityComparerFixture<ValueTuple<byte, E, int>>(); + var v0 = new ValueTuple<byte, E, int>(3, E.RED, 11); + var result = true; + + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + result &= valueTupleFixture.BenchCompare(ref v0, Benchmark.InnerIterationCount); + } + } + + Consume(result); + } + + [Benchmark(InnerIterationCount = Iterations)] + public static void ValueTupleCompareCached() + { + var valueTupleFixture = new EqualityComparerFixture<ValueTuple<byte, E, int>>(); + var v0 = new ValueTuple<byte, E, int>(3, E.RED, 11); + var result = true; + + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + result &= valueTupleFixture.BenchCompareCached(ref v0, Benchmark.InnerIterationCount); + } + } + + Consume(result); + } + + [Benchmark(InnerIterationCount = Iterations)] + public static void ValueTupleCompareWrapped() + { + var valueTupleFixture = new EqualityComparerFixture<ValueTuple<byte, E, int>>(); + var v0 = new ValueTuple<byte, E, int>(3, E.RED, 11); + var result = true; + + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + result &= valueTupleFixture.BenchCompareWrapped(ref v0, Benchmark.InnerIterationCount); + } + } + + Consume(result); + } + + public static int Main() + { + var valueTupleFixture = new EqualityComparerFixture<ValueTuple<byte, E, int>>(); + var v0 = new ValueTuple<byte, E, int>(3, E.RED, 11); + + bool vtCompare = valueTupleFixture.Compare(ref v0, ref v0); + bool vtCompareNoOpt = valueTupleFixture.CompareNoOpt(ref v0, ref v0); + bool vtCompareCached = valueTupleFixture.CompareCached(ref v0, ref v0); + bool vtCompareWrapped = valueTupleFixture.CompareWrapped(ref v0, ref v0); + + bool vtOk = vtCompare & vtCompareNoOpt & vtCompareCached & vtCompareWrapped; + + return vtOk ? 100 : 0; + } + } +} diff --git a/tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.csproj b/tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.csproj new file mode 100644 index 0000000000..7f06379d37 --- /dev/null +++ b/tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.csproj @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{C1BFD48A-A83F-4767-8EB2-3E2C50906681}</ProjectGuid> + <OutputType>Exe</OutputType> + <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir> + <NuGetTargetMoniker>.NETStandard,Version=v1.4</NuGetTargetMoniker> + <NuGetTargetMonikerShort>netstandard1.4</NuGetTargetMonikerShort> + </PropertyGroup> + <!-- Default configurations to help VS understand the configurations --> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + </PropertyGroup> + <ItemGroup> + <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies"> + <Visible>False</Visible> + </CodeAnalysisDependentAssemblyPaths> + </ItemGroup> + <ItemGroup> + <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> + </ItemGroup> + <ItemGroup> + <Compile Include="DefaultEqualityComparerPerf.cs" /> + </ItemGroup> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> + <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup> + <PropertyGroup> + <ProjectAssetsFile>$(JitPackagesConfigFileDirectory)benchmark\obj\project.assets.json</ProjectAssetsFile> + </PropertyGroup> +</Project> |