summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h12
-rw-r--r--src/ToolBox/superpmi/superpmi-shared/lwmlist.h1
-rw-r--r--src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp17
-rw-r--r--src/ToolBox/superpmi/superpmi-shared/methodcontext.h8
-rw-r--r--src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp10
-rw-r--r--src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp8
-rw-r--r--src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp7
-rw-r--r--src/ToolBox/superpmi/superpmi/icorjitinfo.cpp9
-rw-r--r--src/inc/corinfo.h16
-rw-r--r--src/jit/ICorJitInfo_API_names.h1
-rw-r--r--src/jit/ICorJitInfo_API_wrapper.hpp10
-rw-r--r--src/jit/compiler.h3
-rw-r--r--src/jit/flowgraph.cpp107
-rw-r--r--src/jit/gentree.cpp1
-rw-r--r--src/jit/gentree.h4
-rw-r--r--src/jit/importer.cpp173
-rw-r--r--src/jit/morph.cpp2
-rw-r--r--src/jit/namedintrinsiclist.h9
-rw-r--r--src/mscorlib/src/System/Collections/Generic/ComparerHelpers.cs3
-rw-r--r--src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs16
-rw-r--r--src/vm/compile.cpp1
-rw-r--r--src/vm/jitinterface.cpp117
-rw-r--r--src/vm/jitinterface.h8
-rw-r--r--src/vm/mscorlib.h11
-rw-r--r--src/zap/zapinfo.cpp6
-rw-r--r--src/zap/zapinfo.h4
-rw-r--r--tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.cs207
-rw-r--r--tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.csproj40
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>