From df5a9854b4f429a0c799a7f4616030745045b518 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 14 Mar 2017 19:45:10 -0700 Subject: [PATCH] JIT: preliminaries to improve types (dotnet/coreclr#10172) Refactor the logic in `impDevirtualizeCall` so that the part that determines the type of a tree for ref types is now a new utility method that can be called elsewhere. Update the utility to examine calls more closely. For inline candidates that return shared types, try and use the context to get to the unshared version of the type. For calls that are not inline candidates, look at the type in the signature available to the jit w/o context. Call the utility when we've created a temp for an inlinee's argument and the argument is not modified in the inlinee body. If we already thought we knew the type of the temp exactly, ensure that this new information agrees. Rework the logic in `impDevirtualizeCall` in anticipation of interface call devirtualization. Update the diagnostic stream to indicate the kind of call devirtualized and the primary reason why devirtualization happened. Avoid fetching class and method names unless they're going to be used. Likewise try not to fetch attributes if we already have them on hand. Commit migrated from https://github.com/dotnet/coreclr/commit/3ea5447c9b090e621aed9e20a580178e97dabf8f --- src/coreclr/src/jit/compiler.h | 2 + src/coreclr/src/jit/flowgraph.cpp | 33 ++++++ src/coreclr/src/jit/gentree.cpp | 201 ++++++++++++++++++++++++++++++++ src/coreclr/src/jit/importer.cpp | 235 +++++++++++++++++--------------------- 4 files changed, 342 insertions(+), 129 deletions(-) diff --git a/src/coreclr/src/jit/compiler.h b/src/coreclr/src/jit/compiler.h index da5ebbc..7baba5d 100644 --- a/src/coreclr/src/jit/compiler.h +++ b/src/coreclr/src/jit/compiler.h @@ -2188,6 +2188,8 @@ public: CORINFO_CLASS_HANDLE gtGetStructHandleIfPresent(GenTreePtr tree); // Get the handle, and assert if not found. CORINFO_CLASS_HANDLE gtGetStructHandle(GenTreePtr tree); + // Get the handle for a ref type. + CORINFO_CLASS_HANDLE gtGetClassHandle(GenTreePtr tree, bool* isExact, bool* isNonNull); //------------------------------------------------------------------------- // Functions to display the trees diff --git a/src/coreclr/src/jit/flowgraph.cpp b/src/coreclr/src/jit/flowgraph.cpp index 09dfa2a..8bd267c 100644 --- a/src/coreclr/src/jit/flowgraph.cpp +++ b/src/coreclr/src/jit/flowgraph.cpp @@ -22337,6 +22337,39 @@ GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) } else { + // We're going to assign the argument value to the + // temp we use for it in the inline body. + // + // If we know the argument's value can't be + // changed within the method body, try and improve + // the type of the temp. + if (!inlArgInfo[argNum].argHasLdargaOp && !inlArgInfo[argNum].argHasStargOp) + { + GenTree* argNode = inlArgInfo[argNum].argNode; + bool isExact = false; + bool isNonNull = false; + CORINFO_CLASS_HANDLE refClassHandle = gtGetClassHandle(argNode, &isExact, &isNonNull); + + if (refClassHandle != nullptr) + { + const unsigned tmpNum = inlArgInfo[argNum].argTmpNum; + + // If we already had an exact type for + // this temp, this new information had + // better agree with what we knew before. + if (lvaTable[tmpNum].lvClassIsExact) + { + assert(isExact); + assert(refClassHandle == lvaTable[tmpNum].lvClassHnd); + } + else + { + lvaTable[tmpNum].lvClassHnd = refClassHandle; + lvaTable[tmpNum].lvClassIsExact = isExact; + } + } + } + /* Create the temp assignment for this argument */ CORINFO_CLASS_HANDLE structHnd = DUMMY_INIT(0); diff --git a/src/coreclr/src/jit/gentree.cpp b/src/coreclr/src/jit/gentree.cpp index 1ef2302..be8c250 100644 --- a/src/coreclr/src/jit/gentree.cpp +++ b/src/coreclr/src/jit/gentree.cpp @@ -16334,6 +16334,207 @@ CORINFO_CLASS_HANDLE Compiler::gtGetStructHandle(GenTree* tree) return structHnd; } +//------------------------------------------------------------------------ +// gtGetClassHandle: find class handle for a ref type +// +// Arguments: +// tree -- tree to find handle for +// isExact [out] -- whether handle is exact type +// isNonNull [out] -- whether tree value is known not to be null +// +// Return Value: +// nullptr if class handle is unknown, +// otherwise the class handle. +// isExact set true if tree type is known to be exactly the handle type, +// otherwise actual type may be a subtype. +// isNonNull set true if tree value is known not to be null, +// otherwise a null value is possible. + +CORINFO_CLASS_HANDLE Compiler::gtGetClassHandle(GenTreePtr tree, bool* isExact, bool* isNonNull) +{ + // Set default values for our out params. + *isNonNull = false; + *isExact = false; + CORINFO_CLASS_HANDLE objClass = nullptr; + + // Bail out if the tree is not a ref type. + var_types treeType = tree->TypeGet(); + if (treeType != TYP_REF) + { + return objClass; + } + + // Tunnel through commas. + GenTreePtr obj = tree->gtEffectiveVal(false); + const genTreeOps objOp = obj->OperGet(); + + switch (objOp) + { + case GT_COMMA: + { + // gtEffectiveVal above means we shouldn't see commas here. + assert(!"unexpected GT_COMMA"); + break; + } + + case GT_LCL_VAR: + { + // For locals, pick up type info from the local table. + const unsigned objLcl = obj->AsLclVar()->GetLclNum(); + + objClass = lvaTable[objLcl].lvClassHnd; + *isExact = lvaTable[objLcl].lvClassIsExact; + break; + } + + case GT_FIELD: + { + // For fields, get the type from the field handle. + CORINFO_FIELD_HANDLE fieldHnd = obj->gtField.gtFldHnd; + + if (fieldHnd != nullptr) + { + CORINFO_CLASS_HANDLE fieldClass = nullptr; + CorInfoType fieldCorType = info.compCompHnd->getFieldType(fieldHnd, &fieldClass); + if (fieldCorType == CORINFO_TYPE_CLASS) + { + objClass = fieldClass; + } + } + + break; + } + + case GT_RET_EXPR: + { + // If we see a RET_EXPR, recurse through to examine the + // return value expression. + GenTreePtr retExpr = tree->gtRetExpr.gtInlineCandidate; + objClass = gtGetClassHandle(retExpr, isExact, isNonNull); + break; + } + + case GT_CALL: + { + GenTreeCall* call = tree->AsCall(); + if (call->IsInlineCandidate()) + { + // For inline candidates, we've already cached the return + // type class handle in the inline info. + InlineCandidateInfo* inlInfo = call->gtInlineCandidateInfo; + assert(inlInfo != nullptr); + + // Grab it as our first cut at a return type. + assert(inlInfo->methInfo.args.retType == CORINFO_TYPE_CLASS); + objClass = inlInfo->methInfo.args.retTypeClass; + + // If the method is shared, the above may not capture + // the most precise return type information (that is, + // it may represent a shared return type and as such, + // have instances of __Canon). See if we can use the + // context to get at something more definite. + // + // For now, we do this here on demand rather than when + // processing the call, but we could/should apply + // similar sharpening to the argument and local types + // of the inlinee. + const unsigned retClassFlags = info.compCompHnd->getClassAttribs(objClass); + if (retClassFlags & CORINFO_FLG_SHAREDINST) + { + CORINFO_CONTEXT_HANDLE context = inlInfo->exactContextHnd; + + if (context != nullptr) + { + CORINFO_CLASS_HANDLE exactClass = nullptr; + + if (((size_t)context & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS) + { + exactClass = (CORINFO_CLASS_HANDLE)((size_t)context & ~CORINFO_CONTEXTFLAGS_MASK); + } + else + { + CORINFO_METHOD_HANDLE exactMethod = + (CORINFO_METHOD_HANDLE)((size_t)context & ~CORINFO_CONTEXTFLAGS_MASK); + exactClass = info.compCompHnd->getMethodClass(exactMethod); + } + + // Grab the signature in this context. + CORINFO_SIG_INFO sig; + eeGetMethodSig(call->gtCallMethHnd, &sig, exactClass); + assert(sig.retType == CORINFO_TYPE_CLASS); + objClass = sig.retTypeClass; + } + } + } + else if (call->gtCallType == CT_USER_FUNC) + { + // For user calls, we can fetch the approximate return + // type info from the method handle. Unfortunately + // we've lost the exact context, so this is the best + // we can do for now. + CORINFO_METHOD_HANDLE method = call->gtCallMethHnd; + CORINFO_CLASS_HANDLE exactClass = nullptr; + CORINFO_SIG_INFO sig; + eeGetMethodSig(method, &sig, exactClass); + if (sig.retType == CORINFO_TYPE_VOID) + { + // This is a constructor call. + const unsigned methodFlags = info.compCompHnd->getMethodAttribs(method); + assert((methodFlags & CORINFO_FLG_CONSTRUCTOR) != 0); + objClass = info.compCompHnd->getMethodClass(method); + *isExact = true; + *isNonNull = true; + } + else + { + assert(sig.retType == CORINFO_TYPE_CLASS); + objClass = sig.retTypeClass; + } + } + + break; + } + + case GT_CNS_STR: + { + // For literal strings, we know the class and that the + // value is not null. + objClass = impGetStringClass(); + *isExact = true; + *isNonNull = true; + break; + } + + case GT_IND: + { + // indir(addr(lcl)) --> lcl + // + // This comes up during constrained callvirt on ref types. + GenTreeIndir* indir = obj->AsIndir(); + if (indir->HasBase() && !indir->HasIndex()) + { + GenTreePtr base = indir->Base(); + GenTreeLclVarCommon* lcl = base->IsLocalAddrExpr(); + + if ((lcl != nullptr) && (base->OperGet() != GT_ADD)) + { + const unsigned objLcl = lcl->GetLclNum(); + objClass = lvaTable[objLcl].lvClassHnd; + *isExact = lvaTable[objLcl].lvClassIsExact; + } + } + break; + } + + default: + { + break; + } + } + + return objClass; +} + void GenTree::ParseArrayAddress( Compiler* comp, ArrayInfo* arrayInfo, GenTreePtr* pArr, ValueNum* pInxVN, FieldSeqNode** pFldSeq) { diff --git a/src/coreclr/src/jit/importer.cpp b/src/coreclr/src/jit/importer.cpp index 84cfda0..e285fed 100644 --- a/src/coreclr/src/jit/importer.cpp +++ b/src/coreclr/src/jit/importer.cpp @@ -18000,13 +18000,24 @@ BOOL Compiler::impInlineIsGuaranteedThisDerefBeforeAnySideEffects(GenTreePtr ad return TRUE; } -/******************************************************************************/ -// Check the inlining eligibility of this GT_CALL node. -// Mark GTF_CALL_INLINE_CANDIDATE on the GT_CALL node - -// Todo: find a way to record the failure reasons in the IR (or -// otherwise build tree context) so when we do the inlining pass we -// can capture these reasons +//------------------------------------------------------------------------ +// impMarkInlineCandidate: determine if this call can be subsequently inlined +// +// Arguments: +// callNode -- call under scrutiny +// exactContextHnd -- context handle for inlining +// exactContextNeedsRuntimeLookup -- true if context required runtime lookup +// callInfo -- call info from VM +// +// Notes: +// If callNode is an inline candidate, this method sets the flag +// GTF_CALL_INLINE_CANDIDATE, and ensures that helper methods have +// filled in the associated InlineCandidateInfo. +// +// If callNode is not an inline candidate, and the reason is +// something that is inherent to the method being called, the +// method may be marked as "noinline" to short-circuit any +// future assessments of calls to this method. void Compiler::impMarkInlineCandidate(GenTreePtr callNode, CORINFO_CONTEXT_HANDLE exactContextHnd, @@ -18376,110 +18387,33 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, } #if defined(DEBUG) - // Bail if devirt is disabled. if (JitConfig.JitEnableDevirtualization() == 0) { return; } + const bool doPrint = JitConfig.JitPrintDevirtualizedMethods() == 1; #endif // DEBUG - // Do we know anything about the type of the 'this'? - // - // Unfortunately the jit has historcally only kept track of class - // handles for struct types, so the type information needed here - // is missing for many tree nodes. - // - // Even when we can deduce the type, we may not be able to - // devirtualize, but if we can't deduce the type, we can't do - // anything. - CORINFO_CLASS_HANDLE objClass = nullptr; - GenTreePtr obj = thisObj->gtEffectiveVal(false); - const genTreeOps objOp = obj->OperGet(); - bool objIsNonNull = false; - bool isExact = false; + // Fetch information about the virtual method we're calling. + CORINFO_METHOD_HANDLE baseMethod = callInfo->hMethod; + unsigned baseMethodAttribs = callInfo->methodFlags; - switch (objOp) + if (baseMethodAttribs == 0) { - case GT_LCL_VAR: - { - const unsigned objLcl = obj->AsLclVar()->GetLclNum(); - - objClass = lvaTable[objLcl].lvClassHnd; - isExact = lvaTable[objLcl].lvClassIsExact; - break; - } - - case GT_FIELD: - { - CORINFO_FIELD_HANDLE fieldHnd = obj->gtField.gtFldHnd; - - if (fieldHnd != nullptr) - { - CORINFO_CLASS_HANDLE fieldClass = nullptr; - CorInfoType fieldCorType = info.compCompHnd->getFieldType(fieldHnd, &fieldClass); - if (fieldCorType == CORINFO_TYPE_CLASS) - { - objClass = fieldClass; - } - } - - break; - } - - case GT_RET_EXPR: - { - // If we see a RET_EXPR, then obj is the return value from - // an inlineable call. Use the declared return type to - // devirtualize. Note if/when we inline we may get an even - // better type -- so there are opportunities for - // downstream devirtualization that we'll miss right now. - GenTreeCall* objFromCall = obj->gtRetExpr.gtInlineCandidate->AsCall(); - InlineCandidateInfo* inlInfo = objFromCall->gtInlineCandidateInfo; - if (inlInfo != nullptr) - { - assert(inlInfo->methInfo.args.retType == CORINFO_TYPE_CLASS); - objClass = inlInfo->methInfo.args.retTypeClass; - } - - break; - } - - case GT_CNS_STR: - { - objClass = impGetStringClass(); - objIsNonNull = true; - break; - } - - case GT_COMMA: - { - // gtEffectiveVal used above should've burrowed through commas - assert(!"unexpected GT_COMMA"); - break; - } - - default: - { - break; - } + // For late devirt we may not have method attributes, so fetch them. + baseMethodAttribs = info.compCompHnd->getMethodAttribs(baseMethod); } - - // Bail if we can't figure out the type of 'this'. - if (objClass == nullptr) + else { - JITDUMP("impDevirtualizeCall: no type available (op=%s)\n", GenTree::OpName(objOp)); - return; +#if defined(DEBUG) + // Validate that callInfo has up to date method flags + const DWORD freshBaseMethodAttribs = info.compCompHnd->getMethodAttribs(baseMethod); + assert(freshBaseMethodAttribs == baseMethodAttribs); +#endif // DEBUG } - JITDUMP("impDevirtualizeCall: type available, attempting devirt\n"); - - // Fetch VM handles for base method and base class. - CORINFO_METHOD_HANDLE baseMethod = callInfo->hMethod; - CORINFO_CLASS_HANDLE baseClass = info.compCompHnd->getMethodClass(baseMethod); - assert(baseClass != nullptr); - // In R2R mode, we might see virtual stub calls to // non-virtuals. For instance cases where the non-virtual method // is in a different assembly but is called via CALLVIRT. For @@ -18489,43 +18423,60 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // In non-R2R modes CALLVIRT will be turned into a // regular call+nullcheck upstream, so we won't reach this // point. - const DWORD baseMethodAttribs = info.compCompHnd->getMethodAttribs(baseMethod); if ((baseMethodAttribs & CORINFO_FLG_VIRTUAL) == 0) { assert(call->IsVirtualStub()); assert(opts.IsReadyToRun()); - JITDUMP("--- [R2R] base method not virtual, sorry\n"); + JITDUMP("\nimpDevirtualizeCall: [R2R] base method not virtual, sorry\n"); return; } + // 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); + + // Bail if we know nothing. + if (objClass == nullptr) + { + JITDUMP("\nimpDevirtualizeCall: no type available (op=%s)\n", GenTree::OpName(thisObj->OperGet())); + return; + } + + // Fetch information about the class that introduced the virtual method. + CORINFO_CLASS_HANDLE baseClass = info.compCompHnd->getMethodClass(baseMethod); + const DWORD baseClassAttribs = info.compCompHnd->getClassAttribs(baseClass); + + // Is the call an interface call? + const bool isInterface = (baseClassAttribs & CORINFO_FLG_INTERFACE) != 0; + // If the objClass is sealed (final), then we may be able to devirtualize. const DWORD objClassAttribs = info.compCompHnd->getClassAttribs(objClass); const bool objClassIsFinal = (objClassAttribs & CORINFO_FLG_FINAL) != 0; #if defined(DEBUG) - const char* const objClassNote = isExact ? " [exact]" : objClassIsFinal ? " [final]" : ""; - const char* const objClassName = info.compCompHnd->getClassName(objClass); - const char* const baseClassName = info.compCompHnd->getClassName(baseClass); - const char* const baseMethodName = eeGetMethodName(baseMethod, nullptr); + const char* callKind = isInterface ? "interface" : "virtual"; + const char* objClassNote = "[?]"; + const char* objClassName = "?objClass"; + const char* baseClassName = "?baseClass"; + const char* baseMethodName = "?baseMethod"; - if (verbose) + if (verbose || doPrint) { - printf("$$$ In %s: Maybe devirt?\n" - " class for 'this' is %s%s (attrib %08x)\n" - " base method is %s::%s\n", - info.compFullName, objClassName, objClassNote, objClassAttribs, baseClassName, baseMethodName); - } -#endif // defined(DEBUG) + objClassNote = isExact ? " [exact]" : objClassIsFinal ? " [final]" : ""; + objClassName = info.compCompHnd->getClassName(objClass); + baseClassName = info.compCompHnd->getClassName(baseClass); + baseMethodName = eeGetMethodName(baseMethod, nullptr); - // Screen out interface calls. - // We might be able to devirtualize these some day. - const DWORD baseClassAttribs = info.compCompHnd->getClassAttribs(baseClass); - if ((baseClassAttribs & CORINFO_FLG_INTERFACE) != 0) - { - assert(call->IsVirtualStub()); - JITDUMP("--- base class is interface, sorry\n"); - return; + if (verbose) + { + printf("\nimpDevirtualizeCall: Trying to devirtualize %s call:\n" + " class for 'this' is %s%s (attrib %08x)\n" + " base method is %s::%s\n", + callKind, objClassName, objClassNote, objClassAttribs, baseClassName, baseMethodName); + } } +#endif // defined(DEBUG) // Bail if obj class is an interface. // See for instance System.ValueTuple`8::GetHashCode, where lcl 0 is System.IValueTupleInternal @@ -18537,6 +18488,14 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } + // Bail (for now) if base class is an interface. + if (isInterface) + { + assert(call->IsVirtualStub()); + JITDUMP("--- base class is interface, sorry\n"); + return; + } + // Fetch the method that would be called based on the declared type of 'this' CORINFO_METHOD_HANDLE derivedMethod = info.compCompHnd->resolveVirtualMethod(baseMethod, objClass); @@ -18554,13 +18513,31 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, const bool derivedMethodIsFinal = ((derivedMethodAttribs & CORINFO_FLG_FINAL) != 0); #if defined(DEBUG) - const char* const derivedMethodNote = derivedMethodIsFinal ? " [final]" : ""; - const char* derivedClassName; - const char* const derivedMethodName = eeGetMethodName(derivedMethod, &derivedClassName); - if (verbose) + const char* derivedClassName = "?derivedClass"; + const char* derivedMethodName = "?derivedMethod"; + + const char* note = "speculative"; + if (isExact) { - printf(" devirt to %s::%s%s\n", derivedClassName, derivedMethodName, derivedMethodNote); - gtDispTree(call); + note = "exact"; + } + else if (objClassIsFinal) + { + note = "final class"; + } + else if (derivedMethodIsFinal) + { + note = "final method"; + } + + if (verbose || doPrint) + { + derivedMethodName = eeGetMethodName(derivedMethod, &derivedClassName); + if (verbose) + { + printf(" devirt to %s::%s -- %s\n", derivedClassName, derivedMethodName, note); + gtDispTree(call); + } } #endif // defined(DEBUG) @@ -18580,7 +18557,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } - JITDUMP("!!! %s; can devirtualize\n", isExact ? "exact type known" : "final class or method"); + JITDUMP(" %s; can devirtualize\n", note); // Make the updates. call->gtFlags &= ~GTF_CALL_VIRT_VTABLE; @@ -18631,7 +18608,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, call->gtCallMoreFlags &= ~GTF_CALL_M_R2R_REL_INDIRECT; call->setEntryPoint(derivedCallInfo.codePointerLookup.constLookup); } -#endif +#endif // FEATURE_READYTORUN_COMPILER // Need to update call info too. This is fragile // but hopefully the derived method conforms to @@ -18653,10 +18630,10 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, gtDispTree(call); } - if (JitConfig.JitPrintDevirtualizedMethods()) + if (doPrint) { - printf("Devirtualized call to %s:%s to directly call %s:%s\n", baseClassName, baseMethodName, derivedClassName, - derivedMethodName); + printf("Devirtualized %s call to %s:%s; now direct call to %s:%s [%s]\n", callKind, baseClassName, + baseMethodName, derivedClassName, derivedMethodName, note); } #endif // defined(DEBUG) } -- 2.7.4