From b61c1a50907fd2dd836839851d2973140f35a3d2 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 2 Oct 2017 16:28:21 -0700 Subject: [PATCH 1/2] JIT: Wrap some runtime lookups in new node type Work based on the plan outlined in #14305. Introduce a new unary node type GT_RUNTIMELOOKUP that wraps existing runtime lookup trees created in `impLookupToTree` and holds onto the handle that inspired the lookup. Note there are other importer paths that create lookups directly that we might also want to wrap, though wrapping is not a requirement for correctness. Keep this node type around through morph, then unwrap and just use the wrapped node after that. --- src/jit/compiler.h | 3 +++ src/jit/compiler.hpp | 17 ++++++++++++++ src/jit/gentree.cpp | 47 +++++++++++++++++++++++++++++++++++++ src/jit/gentree.h | 56 ++++++++++++++++++++++++++++++++++++++++++++ src/jit/gtlist.h | 2 ++ src/jit/gtstructs.h | 1 + src/jit/importer.cpp | 13 ++++++++-- src/jit/morph.cpp | 3 +++ 8 files changed, 140 insertions(+), 2 deletions(-) diff --git a/src/jit/compiler.h b/src/jit/compiler.h index cd65c25b86d8..0e8b4c213598 100644 --- a/src/jit/compiler.h +++ b/src/jit/compiler.h @@ -2119,6 +2119,8 @@ class Compiler GenTreePtr gtNewAllocObjNode(unsigned int helper, CORINFO_CLASS_HANDLE clsHnd, var_types type, GenTreePtr op1); + GenTree* gtNewRuntimeLookup(CORINFO_GENERIC_HANDLE hnd, CorInfoGenericHandleType hndTyp, GenTree* lookupTree); + //------------------------------------------------------------------------ // Other GenTree functions @@ -9769,6 +9771,7 @@ class GenTreeVisitor case GT_RETURN: case GT_RETFILT: case GT_PHI: + case GT_RUNTIMELOOKUP: { GenTreeUnOp* const unOp = node->AsUnOp(); if (unOp->gtOp1 != nullptr) diff --git a/src/jit/compiler.hpp b/src/jit/compiler.hpp index 4a90760ce2f4..f357e6f5c809 100644 --- a/src/jit/compiler.hpp +++ b/src/jit/compiler.hpp @@ -1169,6 +1169,23 @@ inline GenTreePtr Compiler::gtNewAllocObjNode(unsigned int helper, return node; } +//------------------------------------------------------------------------ +// gtNewRuntimeLookup: Helper to create a runtime lookup node +// +// Arguments: +// hnd - generic handle being looked up +// hndTyp - type of the generic handle +// tree - tree for the lookup +// +// Return Value: +// New GenTreeRuntimeLookup node. +inline GenTree* Compiler::gtNewRuntimeLookup(CORINFO_GENERIC_HANDLE hnd, CorInfoGenericHandleType hndTyp, GenTree* tree) +{ + assert(tree != nullptr); + GenTree* node = new (this, GT_RUNTIMELOOKUP) GenTreeRuntimeLookup(hnd, hndTyp, tree); + return node; +} + /*****************************************************************************/ inline GenTreePtr Compiler::gtNewCodeRef(BasicBlock* block) diff --git a/src/jit/gentree.cpp b/src/jit/gentree.cpp index 540d1686fc4c..25ea00ae9182 100644 --- a/src/jit/gentree.cpp +++ b/src/jit/gentree.cpp @@ -1901,6 +1901,12 @@ unsigned Compiler::gtHashValue(GenTree* tree) reinterpret_cast(tree->gtAllocObj.gtAllocObjClsHnd))); hash = genTreeHashAdd(hash, tree->gtAllocObj.gtNewHelper); break; + case GT_RUNTIMELOOKUP: + hash = + genTreeHashAdd(hash, + static_cast(reinterpret_cast(tree->gtRuntimeLookup.gtHnd))); + break; + case GT_OBJ: hash = genTreeHashAdd(hash, static_cast(reinterpret_cast(tree->gtObj.gtClass))); @@ -5441,6 +5447,7 @@ bool GenTree::TryGetUse(GenTree* def, GenTree*** use) case GT_BLK: case GT_BOX: case GT_ALLOCOBJ: + case GT_RUNTIMELOOKUP: case GT_INIT_VAL: case GT_JTRUE: case GT_SWITCH: @@ -7523,6 +7530,15 @@ GenTreePtr Compiler::gtCloneExpr( } break; + case GT_RUNTIMELOOKUP: + { + GenTreeRuntimeLookup* asRuntimeLookup = tree->AsRuntimeLookup(); + + copy = new (this, GT_RUNTIMELOOKUP) + GenTreeRuntimeLookup(asRuntimeLookup->gtHnd, asRuntimeLookup->gtHndType, asRuntimeLookup->gtOp1); + } + break; + case GT_ARR_LENGTH: copy = gtNewArrLen(tree->TypeGet(), tree->gtOp.gtOp1, tree->gtArrLen.ArrLenOffset()); break; @@ -8842,6 +8858,7 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) case GT_BLK: case GT_BOX: case GT_ALLOCOBJ: + case GT_RUNTIMELOOKUP: case GT_INIT_VAL: case GT_JTRUE: case GT_SWITCH: @@ -10078,6 +10095,31 @@ void Compiler::gtDispNode(GenTreePtr tree, IndentStack* indentStack, __in __in_z { printf(" => [clsHnd=%08X]", dspPtr(tree->gtArgPlace.gtArgPlaceClsHnd)); } + + if (tree->gtOper == GT_RUNTIMELOOKUP) + { +#ifdef _TARGET_64BIT_ + printf(" 0x%llx", dspPtr(tree->gtRuntimeLookup.gtHnd)); +#else + printf(" 0x%x", dspPtr(tree->gtRuntimeLookup.gtHnd)); +#endif + + switch (tree->gtRuntimeLookup.gtHndType) + { + case CORINFO_HANDLETYPE_CLASS: + printf(" class"); + break; + case CORINFO_HANDLETYPE_METHOD: + printf(" method"); + break; + case CORINFO_HANDLETYPE_FIELD: + printf(" field"); + break; + default: + printf(" unknown"); + break; + } + } } // for tracking down problems in reguse prediction or liveness tracking @@ -13210,6 +13252,11 @@ GenTreePtr Compiler::gtFoldExprConst(GenTreePtr tree) return tree; } + if (tree->gtOper == GT_RUNTIMELOOKUP) + { + return tree; + } + if (kind & GTK_UNOP) { assert(op1->OperKind() & GTK_CONST); diff --git a/src/jit/gentree.h b/src/jit/gentree.h index e8803e0e1d48..60d9080dc660 100644 --- a/src/jit/gentree.h +++ b/src/jit/gentree.h @@ -5649,6 +5649,62 @@ struct GenTreeAllocObj final : public GenTreeUnOp #endif }; +// Represents GT_RUNTIMELOOKUP node + +struct GenTreeRuntimeLookup final : public GenTreeUnOp +{ + CORINFO_GENERIC_HANDLE gtHnd; + CorInfoGenericHandleType gtHndType; + + GenTreeRuntimeLookup(CORINFO_GENERIC_HANDLE hnd, CorInfoGenericHandleType hndTyp, GenTree* tree) + : GenTreeUnOp(GT_RUNTIMELOOKUP, tree->gtType, tree DEBUGARG(/*largeNode*/ FALSE)), gtHnd(hnd), gtHndType(hndTyp) + { + assert(hnd != nullptr); + } +#if DEBUGGABLE_GENTREE + GenTreeRuntimeLookup() : GenTreeUnOp() + { + } +#endif + + // Return reference to the actual tree that does the lookup + GenTree*& Lookup() + { + return gtOp1; + } + + bool IsClassHandle() const + { + return gtHndType == CORINFO_HANDLETYPE_CLASS; + } + bool IsMethodHandle() const + { + return gtHndType == CORINFO_HANDLETYPE_METHOD; + } + bool IsFieldHandle() const + { + return gtHndType == CORINFO_HANDLETYPE_FIELD; + } + + // Note these operations describe the handle that is input to the + // lookup, not the handle produced by the lookup. + CORINFO_CLASS_HANDLE GetClassHandle() const + { + assert(IsClassHandle()); + return (CORINFO_CLASS_HANDLE)gtHnd; + } + CORINFO_METHOD_HANDLE GetMethodHandle() const + { + assert(IsMethodHandle()); + return (CORINFO_METHOD_HANDLE)gtHnd; + } + CORINFO_FIELD_HANDLE GetFieldHandle() const + { + assert(IsMethodHandle()); + return (CORINFO_FIELD_HANDLE)gtHnd; + } +}; + // Represents a GT_JCC or GT_SETCC node. struct GenTreeCC final : public GenTree diff --git a/src/jit/gtlist.h b/src/jit/gtlist.h index 939f00c3340c..9ba21a36c8bd 100644 --- a/src/jit/gtlist.h +++ b/src/jit/gtlist.h @@ -99,6 +99,8 @@ GTNODE(ALLOCOBJ , GenTreeAllocObj ,0,GTK_UNOP|GTK_EXOP) // objec GTNODE(INIT_VAL , GenTreeOp ,0,GTK_UNOP) // Initialization value for an initBlk +GTNODE(RUNTIMELOOKUP , GenTreeRuntimeLookup, 0,GTK_UNOP|GTK_EXOP) // Runtime handle lookup + //----------------------------------------------------------------------------- // Binary operators (2 operands): //----------------------------------------------------------------------------- diff --git a/src/jit/gtstructs.h b/src/jit/gtstructs.h index 853ccebe9f8e..02143b49e2ca 100644 --- a/src/jit/gtstructs.h +++ b/src/jit/gtstructs.h @@ -105,6 +105,7 @@ GTSTRUCT_1(PhysReg , GT_PHYSREG) GTSTRUCT_1(SIMD , GT_SIMD) #endif // FEATURE_SIMD GTSTRUCT_1(AllocObj , GT_ALLOCOBJ) +GTSTRUCT_1(RuntimeLookup, GT_RUNTIMELOOKUP) GTSTRUCT_2(CC , GT_JCC, GT_SETCC) #if !defined(LEGACY_BACKEND) && defined(_TARGET_ARM_) #ifdef ARM_SOFTFP diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp index 73310076211a..0ff899143c2a 100644 --- a/src/jit/importer.cpp +++ b/src/jit/importer.cpp @@ -1742,8 +1742,17 @@ GenTreePtr Compiler::impTokenToHandle(CORINFO_RESOLVED_TOKEN* pResolvedToken, } } - return impLookupToTree(pResolvedToken, &embedInfo.lookup, gtTokenToIconFlags(pResolvedToken->token), - embedInfo.compileTimeHandle); + // Generate the full lookup tree. May be null if we're abandoning an inline attempt. + GenTree* result = impLookupToTree(pResolvedToken, &embedInfo.lookup, gtTokenToIconFlags(pResolvedToken->token), + embedInfo.compileTimeHandle); + + // If we have a result and it requires runtime lookup, wrap it in a runtime lookup node. + if ((result != nullptr) && embedInfo.lookup.lookupKind.needsRuntimeLookup) + { + result = gtNewRuntimeLookup(embedInfo.compileTimeHandle, embedInfo.handleType, result); + } + + return result; } GenTreePtr Compiler::impLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index 1bdb2e571645..a1567da3ad74 100644 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -11990,6 +11990,9 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) break; + case GT_RUNTIMELOOKUP: + return fgMorphTree(op1); + #ifdef _TARGET_ARM_ case GT_INTRINSIC: if (tree->gtIntrinsic.gtIntrinsicId == CORINFO_INTRINSIC_Round) From 9fc2875af5c6bde30364d935b6745a61c8927d5f Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 13 Sep 2017 12:25:25 -0700 Subject: [PATCH 2/2] JIT: More enhancements to type equality testing The jit is now able to optimize some type equality checks in shared method instances where one or both of the types require runtime lookup, for instance comparing `T` to a value type, or comparing two different value types `V1` and `V2`. Add two new jit interface methods, one for testing for type equality and the other for type casting. These return Must/MustNot/Maybe results depending on whether or not the equality or cast can be resolved at jit time. Implement the equality check. Use this to enhance the type equality opts in the jit for both direct comparison and the checking done by unbox.any. --- .../superpmi-shared/icorjitinfoimpl.h | 8 +++ .../superpmi/superpmi-shared/lwmlist.h | 2 + .../superpmi-shared/methodcontext.cpp | 66 ++++++++++++++++++ .../superpmi/superpmi-shared/methodcontext.h | 12 +++- .../superpmi-shim-collector/icorjitinfo.cpp | 20 ++++++ .../superpmi-shim-counter/icorjitinfo.cpp | 16 +++++ .../superpmi-shim-simple/icorjitinfo.cpp | 14 ++++ src/ToolBox/superpmi/superpmi/icorjitinfo.cpp | 16 +++++ src/inc/corinfo.h | 33 +++++++-- src/jit/gentree.cpp | 49 +++++++++++++ src/jit/importer.cpp | 29 ++++---- src/jit/morph.cpp | 4 +- src/vm/jitinterface.cpp | 69 +++++++++++++++++++ src/vm/jitinterface.h | 14 ++++ src/zap/zapinfo.cpp | 10 +++ src/zap/zapinfo.h | 3 + 16 files changed, 344 insertions(+), 21 deletions(-) diff --git a/src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h b/src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h index 29baaa8a3927..aae6cd8e5806 100644 --- a/src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h +++ b/src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h @@ -428,6 +428,14 @@ BOOL canCast(CORINFO_CLASS_HANDLE child, // subtype (extends parent) // TRUE if cls1 and cls2 are considered equivalent types. BOOL areTypesEquivalent(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2); +// See if a cast from fromClass to toClass will succeed, fail, or needs +// to be resolved at runtime. +TypeCompareState compareTypesForCast(CORINFO_CLASS_HANDLE fromClass, CORINFO_CLASS_HANDLE toClass); + +// See if types represented by cls1 and cls2 compare equal, not +// equal, or the comparison needs to be resolved at runtime. +TypeCompareState compareTypesForEquality(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2); + // returns is the intersection of cls1 and cls2. CORINFO_CLASS_HANDLE mergeClasses(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2); diff --git a/src/ToolBox/superpmi/superpmi-shared/lwmlist.h b/src/ToolBox/superpmi/superpmi-shared/lwmlist.h index 21f7906510ac..1bca259a6f2f 100644 --- a/src/ToolBox/superpmi/superpmi-shared/lwmlist.h +++ b/src/ToolBox/superpmi/superpmi-shared/lwmlist.h @@ -31,6 +31,8 @@ LWM(CanInlineTypeCheckWithObjectVTable, DWORDLONG, DWORD) LWM(CanSkipMethodVerification, DLD, DWORD) LWM(CanTailCall, Agnostic_CanTailCall, DWORD) LWM(CheckMethodModifier, Agnostic_CheckMethodModifier, DWORD) +LWM(CompareTypesForCast, DLDL, DWORD) +LWM(CompareTypesForEquality, DLDL, DWORD) LWM(CompileMethod, DWORD, Agnostic_CompileMethod) LWM(ConstructStringLiteral, DLD, DLD) LWM(EmbedClassHandle, DWORDLONG, DLDL) diff --git a/src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp b/src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp index 0bf288d1969b..c3ae9d3a5290 100644 --- a/src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp @@ -5288,6 +5288,72 @@ BOOL MethodContext::repAreTypesEquivalent(CORINFO_CLASS_HANDLE cls1, CORINFO_CLA return value; } +void MethodContext::recCompareTypesForCast(CORINFO_CLASS_HANDLE fromClass, CORINFO_CLASS_HANDLE toClass, TypeCompareState result) +{ + if (CompareTypesForCast == nullptr) + CompareTypesForCast = new LightWeightMap(); + + DLDL key; + ZeroMemory(&key, sizeof(DLDL)); // We use the input structs as a key and use memcmp to compare.. so we need to zero + // out padding too + + key.A = (DWORDLONG)fromClass; + key.B = (DWORDLONG)toClass; + + CompareTypesForCast->Add(key, (DWORD)result); +} +void MethodContext::dmpCompareTypesForCast(DLDL key, DWORD value) +{ + printf("CompareTypesForCast key fromClas=%016llX, toClass=%016llx, result=%d", key.A, key.B, value); +} +TypeCompareState MethodContext::repCompareTypesForCast(CORINFO_CLASS_HANDLE fromClass, CORINFO_CLASS_HANDLE toClass) +{ + DLDL key; + ZeroMemory(&key, sizeof(DLDL)); // We use the input structs as a key and use memcmp to compare.. so we need to zero + // out padding too + + key.A = (DWORDLONG)fromClass; + key.B = (DWORDLONG)toClass; + + AssertCodeMsg(CompareTypesForCast->GetIndex(key) != -1, EXCEPTIONCODE_MC, "Didn't find %016llX %016llX", + (DWORDLONG)fromClass, (DWORDLONG)toClass); + TypeCompareState value = (TypeCompareState)CompareTypesForCast->Get(key); + return value; +} + +void MethodContext::recCompareTypesForEquality(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2, TypeCompareState result) +{ + if (CompareTypesForEquality == nullptr) + CompareTypesForEquality = new LightWeightMap(); + + DLDL key; + ZeroMemory(&key, sizeof(DLDL)); // We use the input structs as a key and use memcmp to compare.. so we need to zero + // out padding too + + key.A = (DWORDLONG)cls1; + key.B = (DWORDLONG)cls2; + + CompareTypesForEquality->Add(key, (DWORD)result); +} +void MethodContext::dmpCompareTypesForEquality(DLDL key, DWORD value) +{ + printf("CompareTypesForEquality key cls1=%016llX, cls2=%016llx, result=%d", key.A, key.B, value); +} +TypeCompareState MethodContext::repCompareTypesForEquality(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2) +{ + DLDL key; + ZeroMemory(&key, sizeof(DLDL)); // We use the input structs as a key and use memcmp to compare.. so we need to zero + // out padding too + + key.A = (DWORDLONG)cls1; + key.B = (DWORDLONG)cls2; + + AssertCodeMsg(CompareTypesForEquality->GetIndex(key) != -1, EXCEPTIONCODE_MC, "Didn't find %016llX %016llX", + (DWORDLONG)cls1, (DWORDLONG)cls2); + TypeCompareState value = (TypeCompareState)CompareTypesForEquality->Get(key); + return value; +} + void MethodContext::recFindNameOfToken( CORINFO_MODULE_HANDLE module, mdToken metaTOK, char* szFQName, size_t FQNameCapacity, size_t result) { diff --git a/src/ToolBox/superpmi/superpmi-shared/methodcontext.h b/src/ToolBox/superpmi/superpmi-shared/methodcontext.h index 52515733d33c..9cc8f02ff085 100644 --- a/src/ToolBox/superpmi/superpmi-shared/methodcontext.h +++ b/src/ToolBox/superpmi/superpmi-shared/methodcontext.h @@ -1176,6 +1176,14 @@ class MethodContext void dmpAreTypesEquivalent(DLDL key, DWORD value); BOOL repAreTypesEquivalent(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2); + void recCompareTypesForCast(CORINFO_CLASS_HANDLE fromClass, CORINFO_CLASS_HANDLE toClass, TypeCompareState result); + void dmpCompareTypesForCast(DLDL key, DWORD value); + TypeCompareState repCompareTypesForCast(CORINFO_CLASS_HANDLE fromClass, CORINFO_CLASS_HANDLE toClass); + + void recCompareTypesForEquality(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2, TypeCompareState result); + void dmpCompareTypesForEquality(DLDL key, DWORD value); + TypeCompareState repCompareTypesForEquality(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2); + void recFindNameOfToken( CORINFO_MODULE_HANDLE module, mdToken metaTOK, char* szFQName, size_t FQNameCapacity, size_t result); void dmpFindNameOfToken(DLD key, DLD value); @@ -1257,7 +1265,7 @@ class MethodContext }; // ********************* Please keep this up-to-date to ease adding more *************** -// Highest packet number: 162 +// Highest packet number: 164 // ************************************************************************************* enum mcPackets { @@ -1279,6 +1287,8 @@ enum mcPackets Packet_CheckMethodModifier = 142, // retired as 13 on 2013/07/04 Retired3 = 14, Retired5 = 141, // retired as 14 on 2013/07/03 + Packet_CompareTypesForCast = 163, // Added 10/4/17 + Packet_CompareTypesForEquality = 164, // Added 10/4/17 Packet_CompileMethod = 143, // retired as 141 on 2013/07/09 Packet_ConstructStringLiteral = 15, Packet_EmbedClassHandle = 16, diff --git a/src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 6051de8840e3..af3e8c94de33 100644 --- a/src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -889,6 +889,26 @@ BOOL interceptor_ICJI::areTypesEquivalent(CORINFO_CLASS_HANDLE cls1, CORINFO_CLA return temp; } +// See if a cast from fromClass to toClass will succeed, fail, or needs +// to be resolved at runtime. +TypeCompareState interceptor_ICJI::compareTypesForCast(CORINFO_CLASS_HANDLE fromClass, CORINFO_CLASS_HANDLE toClass) +{ + mc->cr->AddCall("compareTypesForCast"); + TypeCompareState temp = original_ICorJitInfo->compareTypesForCast(fromClass, toClass); + mc->recCompareTypesForCast(fromClass, toClass, temp); + return temp; +} + +// See if types represented by cls1 and cls2 compare equal, not +// equal, or the comparison needs to be resolved at runtime. +TypeCompareState interceptor_ICJI::compareTypesForEquality(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2) +{ + mc->cr->AddCall("compareTypesForEquality"); + TypeCompareState temp = original_ICorJitInfo->compareTypesForEquality(cls1, cls2); + mc->recCompareTypesForEquality(cls1, cls2, temp); + return temp; +} + // returns is the intersection of cls1 and cls2. CORINFO_CLASS_HANDLE interceptor_ICJI::mergeClasses(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2) { diff --git a/src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp b/src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp index 17da100dac50..dd047e83a7de 100644 --- a/src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp +++ b/src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp @@ -703,6 +703,22 @@ BOOL interceptor_ICJI::areTypesEquivalent(CORINFO_CLASS_HANDLE cls1, CORINFO_CLA return original_ICorJitInfo->areTypesEquivalent(cls1, cls2); } +// See if a cast from fromClass to toClass will succeed, fail, or needs +// to be resolved at runtime. +TypeCompareState interceptor_ICJI::compareTypesForCast(CORINFO_CLASS_HANDLE fromClass, CORINFO_CLASS_HANDLE toClass) +{ + mcs->AddCall("compareTypesForCast"); + return original_ICorJitInfo->compareTypesForCast(fromClass, toClass); +} + +// See if types represented by cls1 and cls2 compare equal, not +// equal, or the comparison needs to be resolved at runtime. +TypeCompareState interceptor_ICJI::compareTypesForEquality(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2) +{ + mcs->AddCall("compareTypesForEquality"); + return original_ICorJitInfo->compareTypesForEquality(cls1, cls2); +} + // returns is the intersection of cls1 and cls2. CORINFO_CLASS_HANDLE interceptor_ICJI::mergeClasses(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2) { diff --git a/src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp b/src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp index df4cfcfbcd1d..cc384f31a0e2 100644 --- a/src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp +++ b/src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp @@ -629,6 +629,20 @@ BOOL interceptor_ICJI::areTypesEquivalent(CORINFO_CLASS_HANDLE cls1, CORINFO_CLA return original_ICorJitInfo->areTypesEquivalent(cls1, cls2); } +// See if a cast from fromClass to toClass will succeed, fail, or needs +// to be resolved at runtime. +TypeCompareState interceptor_ICJI::compareTypesForCast(CORINFO_CLASS_HANDLE fromClass, CORINFO_CLASS_HANDLE toClass) +{ + return original_ICorJitInfo->compareTypesForCast(fromClass, toClass); +} + +// See if types represented by cls1 and cls2 compare equal, not +// equal, or the comparison needs to be resolved at runtime. +TypeCompareState interceptor_ICJI::compareTypesForEquality(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2) +{ + return original_ICorJitInfo->compareTypesForEquality(cls1, cls2); +} + // returns is the intersection of cls1 and cls2. CORINFO_CLASS_HANDLE interceptor_ICJI::mergeClasses(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2) { diff --git a/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp b/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp index 7c119b86dd37..e16903b9e147 100644 --- a/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp +++ b/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp @@ -760,6 +760,22 @@ BOOL MyICJI::areTypesEquivalent(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE return jitInstance->mc->repAreTypesEquivalent(cls1, cls2); } +// See if a cast from fromClass to toClass will succeed, fail, or needs +// to be resolved at runtime. +TypeCompareState MyICJI::compareTypesForCast(CORINFO_CLASS_HANDLE fromClass, CORINFO_CLASS_HANDLE toClass) +{ + jitInstance->mc->cr->AddCall("compareTypesForCast"); + return jitInstance->mc->repCompareTypesForCast(fromClass, toClass); +} + +// See if types represented by cls1 and cls2 compare equal, not +// equal, or the comparison needs to be resolved at runtime. +TypeCompareState MyICJI::compareTypesForEquality(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2) +{ + jitInstance->mc->cr->AddCall("compareTypesForEquality"); + return jitInstance->mc->repCompareTypesForEquality(cls1, cls2); +} + // returns is the intersection of cls1 and cls2. CORINFO_CLASS_HANDLE MyICJI::mergeClasses(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2) { diff --git a/src/inc/corinfo.h b/src/inc/corinfo.h index b03f0456ac39..dc3d22fa9f2b 100644 --- a/src/inc/corinfo.h +++ b/src/inc/corinfo.h @@ -213,12 +213,11 @@ TODO: Talk about initializing strutures before use #define SELECTANY extern __declspec(selectany) #endif -// {CFEC7B89-D5FF-4A67-823A-EF99FE0286F4} -SELECTANY const GUID JITEEVersionIdentifier = { - 0xcfec7b89, - 0xd5ff, - 0x4a67, - { 0x82, 0x3a, 0xef, 0x99, 0xfe, 0x2, 0x86, 0xf4 } +SELECTANY const GUID JITEEVersionIdentifier = { /* 8f51c68e-d515-425c-9e04-97e4a8148b07 */ + 0x8f51c68e, + 0xd515, + 0x425c, + {0x9e, 0x04, 0x97, 0xe4, 0xa8, 0x14, 0x8b, 0x07} }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1955,6 +1954,14 @@ typedef SIZE_T GSCookie; const int MAX_EnC_HANDLER_NESTING_LEVEL = 6; +// Results from type comparison queries +enum class TypeCompareState +{ + MustNot = -1, // types are not equal + May = 0, // types may be equal (must test at runtime) + Must = 1, // type are equal +}; + // // This interface is logically split into sections for each class of information // (ICorMethodInfo, ICorModuleInfo, etc.). This split used to exist physically as well @@ -2492,6 +2499,20 @@ class ICorStaticInfo CORINFO_CLASS_HANDLE cls2 ) = 0; + // See if a cast from fromClass to toClass will succeed, fail, or needs + // to be resolved at runtime. + virtual TypeCompareState compareTypesForCast( + CORINFO_CLASS_HANDLE fromClass, + CORINFO_CLASS_HANDLE toClass + ) = 0; + + // See if types represented by cls1 and cls2 compare equal, not + // equal, or the comparison needs to be resolved at runtime. + virtual TypeCompareState compareTypesForEquality( + CORINFO_CLASS_HANDLE cls1, + CORINFO_CLASS_HANDLE cls2 + ) = 0; + // returns is the intersection of cls1 and cls2. virtual CORINFO_CLASS_HANDLE mergeClasses( CORINFO_CLASS_HANDLE cls1, diff --git a/src/jit/gentree.cpp b/src/jit/gentree.cpp index 25ea00ae9182..a4ea1036411e 100644 --- a/src/jit/gentree.cpp +++ b/src/jit/gentree.cpp @@ -12407,6 +12407,55 @@ GenTree* Compiler::gtFoldTypeCompare(GenTree* tree) } } + // If the inputs to the type from handle operations are now + // either known class handles or runtime lookups, ask the VM + // if it knows the outcome of the equality comparison. + CORINFO_CLASS_HANDLE cls1Hnd = nullptr; + CORINFO_CLASS_HANDLE cls2Hnd = nullptr; + unsigned runtimeLookupCount = 0; + + if ((op1ClassFromHandle->OperGet() == GT_CNS_INT) && op1ClassFromHandle->IsIconHandle(GTF_ICON_CLASS_HDL)) + { + cls1Hnd = (CORINFO_CLASS_HANDLE)op1ClassFromHandle->gtIntCon.gtCompileTimeHandle; + } + else if (op1ClassFromHandle->OperGet() == GT_RUNTIMELOOKUP) + { + cls1Hnd = op1ClassFromHandle->AsRuntimeLookup()->GetClassHandle(); + runtimeLookupCount++; + } + + if ((op2ClassFromHandle->OperGet() == GT_CNS_INT) && op2ClassFromHandle->IsIconHandle(GTF_ICON_CLASS_HDL)) + { + cls2Hnd = (CORINFO_CLASS_HANDLE)op2ClassFromHandle->gtIntCon.gtCompileTimeHandle; + } + else if (op2ClassFromHandle->OperGet() == GT_RUNTIMELOOKUP) + { + cls2Hnd = op2ClassFromHandle->AsRuntimeLookup()->GetClassHandle(); + runtimeLookupCount++; + } + + if ((cls1Hnd != nullptr) && (cls2Hnd != nullptr)) + { + TypeCompareState s = info.compCompHnd->compareTypesForEquality(cls1Hnd, cls2Hnd); + + if (s != TypeCompareState::May) + { + // Type comparison result is known. + const bool typesAreEqual = (s == TypeCompareState::Must); + const bool operatorIsEQ = (oper == GT_EQ); + const int compareResult = operatorIsEQ ^ typesAreEqual ? 0 : 1; + JITDUMP("Runtime reports comparison is known at jit time: %u\n", compareResult); + GenTree* result = gtNewIconNode(compareResult); + + // The runtime lookups are now dead code, so we may not + // need the generic context kept alive either. + assert(lvaGenericsContextUseCount >= runtimeLookupCount); + lvaGenericsContextUseCount -= runtimeLookupCount; + return result; + } + } + + // We can't answer definitively at jit time, but can still simplfy the comparison. GenTree* compare = gtNewOperNode(oper, TYP_INT, op1ClassFromHandle, op2ClassFromHandle); // Drop any now-irrelvant flags diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp index 0ff899143c2a..332abbf1c7b0 100644 --- a/src/jit/importer.cpp +++ b/src/jit/importer.cpp @@ -14661,20 +14661,25 @@ void Compiler::impImportBlockCode(BasicBlock* block) // Look ahead for unbox.any if (codeAddr + (sz + 1 + sizeof(mdToken)) <= codeEndp && codeAddr[sz] == CEE_UNBOX_ANY) { - DWORD classAttribs = info.compCompHnd->getClassAttribs(resolvedToken.hClass); - if (!(classAttribs & CORINFO_FLG_SHAREDINST)) - { - CORINFO_RESOLVED_TOKEN unboxResolvedToken; + CORINFO_RESOLVED_TOKEN unboxResolvedToken; - impResolveToken(codeAddr + (sz + 1), &unboxResolvedToken, CORINFO_TOKENKIND_Class); + impResolveToken(codeAddr + (sz + 1), &unboxResolvedToken, CORINFO_TOKENKIND_Class); - if (unboxResolvedToken.hClass == resolvedToken.hClass) - { - JITDUMP("\n Importing BOX; UNBOX.ANY as NOP\n"); - // Skip the next unbox.any instruction - sz += sizeof(mdToken) + 1; - break; - } + // See if token types a equal. + const TypeCompareState compare = + info.compCompHnd->compareTypesForEquality(unboxResolvedToken.hClass, resolvedToken.hClass); + + // If so, box/unbox.any is a nop. + if (compare == TypeCompareState::Must) + { + JITDUMP("\n Importing BOX; UNBOX.ANY as NOP\n"); + // Skip the next unbox.any instruction + sz += sizeof(mdToken) + 1; + break; + } + else + { + assert(unboxResolvedToken.hClass != resolvedToken.hClass); } } diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index a1567da3ad74..c4337b6e2300 100644 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -11990,8 +11990,8 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) break; - case GT_RUNTIMELOOKUP: - return fgMorphTree(op1); + case GT_RUNTIMELOOKUP: + return fgMorphTree(op1); #ifdef _TARGET_ARM_ case GT_INTRINSIC: diff --git a/src/vm/jitinterface.cpp b/src/vm/jitinterface.cpp index aa4106419845..5a5ff05cd664 100644 --- a/src/vm/jitinterface.cpp +++ b/src/vm/jitinterface.cpp @@ -4584,6 +4584,75 @@ BOOL CEEInfo::areTypesEquivalent( return result; } +/*********************************************************************/ +// See if a cast from fromClass to toClass will succeed, fail, or needs +// to be resolved at runtime. +TypeCompareState CEEInfo::compareTypesForCast( + CORINFO_CLASS_HANDLE fromClass, + CORINFO_CLASS_HANDLE toClass) +{ + // Stub for now + return TypeCompareState::May; +} + +/*********************************************************************/ +// See if types represented by cls1 and cls2 compare equal, not +// equal, or the comparison needs to be resolved at runtime. +TypeCompareState CEEInfo::compareTypesForEquality( + CORINFO_CLASS_HANDLE cls1, + CORINFO_CLASS_HANDLE cls2) +{ + CONTRACTL { + SO_TOLERANT; + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + TypeCompareState result = TypeCompareState::May; + + JIT_TO_EE_TRANSITION(); + + TypeHandle hnd1 = (TypeHandle) cls1; + TypeHandle hnd2 = (TypeHandle) cls2; + + // If neither type is a canonical subtype, type handle comparison suffices + if (!hnd1.IsCanonicalSubtype() && !hnd2.IsCanonicalSubtype()) + { + result = (hnd1 == hnd2 ? TypeCompareState::Must : TypeCompareState::MustNot); + } + // If either or both types are canonical subtypes, we can sometimes prove inequality. + else + { + // If either is a value type then the types cannot + // be equal unless the type defs are the same. + if (hnd1.IsValueType() || hnd2.IsValueType()) + { + if (!hnd1.GetMethodTable()->HasSameTypeDefAs(hnd2.GetMethodTable())) + { + result = TypeCompareState::MustNot; + } + } + // If we have two ref types that are not __Canon, then the + // types cannot be equal unless the type defs are the same. + else + { + TypeHandle canonHnd = TypeHandle(g_pCanonMethodTableClass); + if ((hnd1 != canonHnd) && (hnd2 != canonHnd)) + { + if (!hnd1.GetMethodTable()->HasSameTypeDefAs(hnd2.GetMethodTable())) + { + result = TypeCompareState::MustNot; + } + } + } + } + + EE_TO_JIT_TRANSITION(); + + return result; +} + /*********************************************************************/ // returns is the intersection of cls1 and cls2. CORINFO_CLASS_HANDLE CEEInfo::mergeClasses( diff --git a/src/vm/jitinterface.h b/src/vm/jitinterface.h index 56394e301604..aa140b00a01a 100644 --- a/src/vm/jitinterface.h +++ b/src/vm/jitinterface.h @@ -562,6 +562,20 @@ class CEEInfo : public ICorJitInfo CORINFO_CLASS_HANDLE cls2 ); + // See if a cast from fromClass to toClass will succeed, fail, or needs + // to be resolved at runtime. + TypeCompareState compareTypesForCast( + CORINFO_CLASS_HANDLE fromClass, + CORINFO_CLASS_HANDLE toClass + ); + + // See if types represented by cls1 and cls2 compare equal, not + // equal, or the comparison needs to be resolved at runtime. + TypeCompareState compareTypesForEquality( + CORINFO_CLASS_HANDLE cls1, + CORINFO_CLASS_HANDLE cls2 + ); + // returns is the intersection of cls1 and cls2. CORINFO_CLASS_HANDLE mergeClasses( CORINFO_CLASS_HANDLE cls1, diff --git a/src/zap/zapinfo.cpp b/src/zap/zapinfo.cpp index 01d3a3b7cc9e..4007f678ae3a 100644 --- a/src/zap/zapinfo.cpp +++ b/src/zap/zapinfo.cpp @@ -3107,6 +3107,16 @@ BOOL ZapInfo::areTypesEquivalent(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE return m_pEEJitInfo->areTypesEquivalent(cls1, cls2); } +TypeCompareState ZapInfo::compareTypesForCast(CORINFO_CLASS_HANDLE fromClass, CORINFO_CLASS_HANDLE toClass) +{ + return m_pEEJitInfo->compareTypesForCast(fromClass, toClass); +} + +TypeCompareState ZapInfo::compareTypesForEquality(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2) +{ + return m_pEEJitInfo->compareTypesForEquality(cls1, cls2); +} + CORINFO_CLASS_HANDLE ZapInfo::mergeClasses( CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2) diff --git a/src/zap/zapinfo.h b/src/zap/zapinfo.h index 5bb86dd88aae..f401cbaf9dc2 100644 --- a/src/zap/zapinfo.h +++ b/src/zap/zapinfo.h @@ -573,6 +573,9 @@ class ZapInfo CorInfoType getTypeForPrimitiveValueClass(CORINFO_CLASS_HANDLE cls); BOOL canCast(CORINFO_CLASS_HANDLE child, CORINFO_CLASS_HANDLE parent); BOOL areTypesEquivalent(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2); + TypeCompareState compareTypesForCast(CORINFO_CLASS_HANDLE fromClass, CORINFO_CLASS_HANDLE toClass); + TypeCompareState compareTypesForEquality(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2); + CORINFO_CLASS_HANDLE mergeClasses(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2); BOOL shouldEnforceCallvirtRestriction(CORINFO_MODULE_HANDLE scope);