diff --git a/src/jit/compiler.h b/src/jit/compiler.h index 3099c44a0d26..a28e60b6b920 100644 --- a/src/jit/compiler.h +++ b/src/jit/compiler.h @@ -252,8 +252,10 @@ class LclVarDsc unsigned char lvStackByref : 1; // This is a compiler temporary of TYP_BYREF that is known to point into our local // stack frame. - unsigned char lvArgWrite : 1; // variable is a parameter and STARG was used on it - unsigned char lvIsTemp : 1; // Short-lifetime compiler temp + unsigned char lvHasILStoreOp : 1; // there is at least one STLOC or STARG on this local + unsigned char lvHasMultipleILStoreOp : 1; // there is more than one STLOC on this local + + unsigned char lvIsTemp : 1; // Short-lifetime compiler temp #if OPT_BOOL_OPS unsigned char lvIsBoolean : 1; // set if variable is boolean #endif @@ -324,6 +326,10 @@ class LclVarDsc unsigned char lvClassIsExact : 1; // lvClassHandle is the exact type +#ifdef DEBUG + unsigned char lvClassInfoUpdated : 1; // true if this var has updated class handle or exactness +#endif + union { unsigned lvFieldLclStart; // The index of the local var representing the first field in the promoted struct // local. @@ -2672,11 +2678,16 @@ class Compiler // Returns true if this local var is a multireg struct bool lvaIsMultiregStruct(LclVarDsc* varDsc); - // If the class is a TYP_STRUCT, get/set a class handle describing it - + // If the local is a TYP_STRUCT, get/set a class handle describing it CORINFO_CLASS_HANDLE lvaGetStruct(unsigned varNum); void lvaSetStruct(unsigned varNum, CORINFO_CLASS_HANDLE typeHnd, bool unsafeValueClsCheck, bool setTypeInfo = true); + // If the local is TYP_REF, set or update the associated class information. + void lvaSetClass(unsigned varNum, CORINFO_CLASS_HANDLE clsHnd, bool isExact = false); + void lvaSetClass(unsigned varNum, GenTreePtr tree, CORINFO_CLASS_HANDLE stackHandle = nullptr); + void lvaUpdateClass(unsigned varNum, CORINFO_CLASS_HANDLE clsHnd, bool isExact = false); + void lvaUpdateClass(unsigned varNum, GenTreePtr tree, CORINFO_CLASS_HANDLE stackHandle = nullptr); + #define MAX_NumOfFieldsInPromotableStruct 4 // Maximum number of fields in promotable struct // Info about struct fields diff --git a/src/jit/compiler.hpp b/src/jit/compiler.hpp index 164971066a9d..002694776fcb 100644 --- a/src/jit/compiler.hpp +++ b/src/jit/compiler.hpp @@ -2530,10 +2530,10 @@ inline BOOL Compiler::lvaIsOriginalThisArg(unsigned varNum) // copy to a new local, and mark the original as DoNotEnregister, to // ensure that it is stack-allocated. It should not be the case that the original one can be modified -- it // should not be written to, or address-exposed. - assert(!varDsc->lvArgWrite && + assert(!varDsc->lvHasILStoreOp && (!varDsc->lvAddrExposed || ((info.compMethodInfo->options & CORINFO_GENERICS_CTXT_FROM_THIS) != 0))); #else - assert(!varDsc->lvArgWrite && !varDsc->lvAddrExposed); + assert(!varDsc->lvHasILStoreOp && !varDsc->lvAddrExposed); #endif } #endif diff --git a/src/jit/flowgraph.cpp b/src/jit/flowgraph.cpp index b21c29ebe206..29d994594d3b 100644 --- a/src/jit/flowgraph.cpp +++ b/src/jit/flowgraph.cpp @@ -4271,7 +4271,7 @@ class FgStack // jumpTarget[N] is set to a JT_* value if IL offset N is a // jump target in the method. // -// Also sets lvAddrExposed and lvArgWrite in lvaTable[]. +// Also sets lvAddrExposed and lvHasILStoreOp, ilHasMultipleILStoreOp in lvaTable[]. #ifdef _PREFAST_ #pragma warning(push) @@ -4548,12 +4548,67 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, BYTE* // account for possible hidden param varNum = compMapILargNum(varNum); + // This check is only intended to prevent an AV. Bad varNum values will later + // be handled properly by the verifier. + if (varNum < lvaTableCnt) + { + // In non-inline cases, note written-to arguments. + lvaTable[varNum].lvHasILStoreOp = 1; + } + } + } + break; + + case CEE_STLOC_0: + case CEE_STLOC_1: + case CEE_STLOC_2: + case CEE_STLOC_3: + varNum = (opcode - CEE_STLOC_0); + goto STLOC; + + case CEE_STLOC: + case CEE_STLOC_S: + { + noway_assert(sz == sizeof(BYTE) || sz == sizeof(WORD)); + + if (codeAddr > codeEndp - sz) + { + goto TOO_FAR; + } + + varNum = (sz == sizeof(BYTE)) ? getU1LittleEndian(codeAddr) : getU2LittleEndian(codeAddr); + + STLOC: + if (isInlining) + { + InlLclVarInfo& lclInfo = impInlineInfo->lclVarInfo[varNum + impInlineInfo->argCnt]; + + if (lclInfo.lclHasStlocOp) + { + lclInfo.lclHasMultipleStlocOp = 1; + } + else + { + lclInfo.lclHasStlocOp = 1; + } + } + else + { + varNum += info.compArgsCount; + // This check is only intended to prevent an AV. Bad varNum values will later // be handled properly by the verifier. if (varNum < lvaTableCnt) { // In non-inline cases, note written-to locals. - lvaTable[varNum].lvArgWrite = 1; + if (lvaTable[varNum].lvHasILStoreOp) + { + lvaTable[varNum].lvHasMultipleILStoreOp = 1; + } + else + { + lvaTable[varNum].lvHasILStoreOp = 1; + } } } } @@ -4875,11 +4930,11 @@ void Compiler::fgAdjustForAddressExposedOrWrittenThis() // Optionally enable adjustment during stress. if (!tiVerificationNeeded && compStressCompile(STRESS_GENERIC_VARN, 15)) { - lvaTable[info.compThisArg].lvArgWrite = true; + lvaTable[info.compThisArg].lvHasILStoreOp = true; } // If this is exposed or written to, create a temp for the modifiable this - if (lvaTable[info.compThisArg].lvAddrExposed || lvaTable[info.compThisArg].lvArgWrite) + if (lvaTable[info.compThisArg].lvAddrExposed || lvaTable[info.compThisArg].lvHasILStoreOp) { // If there is a "ldarga 0" or "starg 0", grab and use the temp. lvaArg0Var = lvaGrabTemp(false DEBUGARG("Address-exposed, or written this pointer")); @@ -4893,14 +4948,14 @@ void Compiler::fgAdjustForAddressExposedOrWrittenThis() lvaTable[lvaArg0Var].lvLclFieldExpr = lvaTable[info.compThisArg].lvLclFieldExpr; lvaTable[lvaArg0Var].lvLiveAcrossUCall = lvaTable[info.compThisArg].lvLiveAcrossUCall; #endif - lvaTable[lvaArg0Var].lvArgWrite = lvaTable[info.compThisArg].lvArgWrite; - lvaTable[lvaArg0Var].lvVerTypeInfo = lvaTable[info.compThisArg].lvVerTypeInfo; + lvaTable[lvaArg0Var].lvHasILStoreOp = lvaTable[info.compThisArg].lvHasILStoreOp; + lvaTable[lvaArg0Var].lvVerTypeInfo = lvaTable[info.compThisArg].lvVerTypeInfo; // Clear the TI_FLAG_THIS_PTR in the original 'this' pointer. noway_assert(lvaTable[lvaArg0Var].lvVerTypeInfo.IsThisPtr()); lvaTable[info.compThisArg].lvVerTypeInfo.ClearThisPtr(); - lvaTable[info.compThisArg].lvAddrExposed = false; - lvaTable[info.compThisArg].lvArgWrite = false; + lvaTable[info.compThisArg].lvAddrExposed = false; + lvaTable[info.compThisArg].lvHasILStoreOp = false; } } @@ -8034,8 +8089,8 @@ void Compiler::fgAddInternal() lva0CopiedForGenericsCtxt = false; #endif // JIT32_GCENCODER noway_assert(lva0CopiedForGenericsCtxt || !lvaTable[info.compThisArg].lvAddrExposed); - noway_assert(!lvaTable[info.compThisArg].lvArgWrite); - noway_assert(lvaTable[lvaArg0Var].lvAddrExposed || lvaTable[lvaArg0Var].lvArgWrite || + noway_assert(!lvaTable[info.compThisArg].lvHasILStoreOp); + noway_assert(lvaTable[lvaArg0Var].lvAddrExposed || lvaTable[lvaArg0Var].lvHasILStoreOp || lva0CopiedForGenericsCtxt); var_types thisType = lvaTable[info.compThisArg].TypeGet(); @@ -20428,10 +20483,11 @@ void Compiler::fgDebugCheckBBlist(bool checkBBNum /* = false */, bool checkBBRef // Should never expose the address of arg 0 or write to arg 0. // In addition, lvArg0Var should remain 0 if arg0 is not // written to or address-exposed. - noway_assert(compThisArgAddrExposedOK && !lvaTable[info.compThisArg].lvArgWrite && - (lvaArg0Var == info.compThisArg || - lvaArg0Var != info.compThisArg && (lvaTable[lvaArg0Var].lvAddrExposed || - lvaTable[lvaArg0Var].lvArgWrite || copiedForGenericsCtxt))); + noway_assert( + compThisArgAddrExposedOK && !lvaTable[info.compThisArg].lvHasILStoreOp && + (lvaArg0Var == info.compThisArg || + lvaArg0Var != info.compThisArg && + (lvaTable[lvaArg0Var].lvAddrExposed || lvaTable[lvaArg0Var].lvHasILStoreOp || copiedForGenericsCtxt))); } } @@ -22331,9 +22387,13 @@ GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) for (unsigned argNum = 0; argNum < inlineInfo->argCnt; argNum++) { - if (inlArgInfo[argNum].argHasTmp) + const InlArgInfo& argInfo = inlArgInfo[argNum]; + const bool argIsSingleDef = !argInfo.argHasLdargaOp && !argInfo.argHasStargOp; + GenTree* const argNode = inlArgInfo[argNum].argNode; + + if (argInfo.argHasTmp) { - noway_assert(inlArgInfo[argNum].argIsUsed); + noway_assert(argInfo.argIsUsed); /* argBashTmpNode is non-NULL iff the argument's value was referenced exactly once by the original IL. This offers an @@ -22347,14 +22407,12 @@ GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) once) but the optimization cannot be applied. */ - GenTreePtr argSingleUseNode = inlArgInfo[argNum].argBashTmpNode; + GenTreePtr argSingleUseNode = argInfo.argBashTmpNode; - if (argSingleUseNode && !(argSingleUseNode->gtFlags & GTF_VAR_CLONED) && - !inlArgInfo[argNum].argHasLdargaOp && !inlArgInfo[argNum].argHasStargOp) + if ((argSingleUseNode != nullptr) && !(argSingleUseNode->gtFlags & GTF_VAR_CLONED) && argIsSingleDef) { // Change the temp in-place to the actual argument. // We currently do not support this for struct arguments, so it must not be a GT_OBJ. - GenTree* argNode = inlArgInfo[argNum].argNode; assert(argNode->gtOper != GT_OBJ); argSingleUseNode->CopyFrom(argNode, this); continue; @@ -22363,44 +22421,15 @@ GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) { // 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 */ + const unsigned tmpNum = argInfo.argTmpNum; + const var_types argType = lclVarInfo[argNum].lclTypeInfo; + // Create the temp assignment for this argument CORINFO_CLASS_HANDLE structHnd = DUMMY_INIT(0); - if (varTypeIsStruct(lclVarInfo[argNum].lclTypeInfo)) + if (varTypeIsStruct(argType)) { - structHnd = gtGetStructHandleIfPresent(inlArgInfo[argNum].argNode); + structHnd = gtGetStructHandleIfPresent(argNode); noway_assert(structHnd != NO_CLASS_HANDLE); } @@ -22408,8 +22437,16 @@ GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) // argTmpNum here since in-linee compiler instance // would have iterated over these and marked them // accordingly. - impAssignTempGen(inlArgInfo[argNum].argTmpNum, inlArgInfo[argNum].argNode, structHnd, - (unsigned)CHECK_SPILL_NONE, &afterStmt, callILOffset, block); + impAssignTempGen(tmpNum, argNode, structHnd, (unsigned)CHECK_SPILL_NONE, &afterStmt, callILOffset, + block); + + // If we know the argument's value can't be + // changed within the method body, try and improve + // the type of the temp. + if (argIsSingleDef && (argType == TYP_REF)) + { + lvaUpdateClass(tmpNum, argNode); + } #ifdef DEBUG if (verbose) @@ -22419,44 +22456,42 @@ GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) #endif // DEBUG } } - else if (inlArgInfo[argNum].argIsByRefToStructLocal) + else if (argInfo.argIsByRefToStructLocal) { - // Do nothing. + // Do nothing. Arg was directly substituted as we read + // the inlinee. } else { /* The argument is either not used or a const or lcl var */ - noway_assert(!inlArgInfo[argNum].argIsUsed || inlArgInfo[argNum].argIsInvariant || - inlArgInfo[argNum].argIsLclVar); + noway_assert(!argInfo.argIsUsed || argInfo.argIsInvariant || argInfo.argIsLclVar); /* Make sure we didnt change argNode's along the way, or else subsequent uses of the arg would have worked with the bashed value */ - if (inlArgInfo[argNum].argIsInvariant) + if (argInfo.argIsInvariant) { - assert(inlArgInfo[argNum].argNode->OperIsConst() || inlArgInfo[argNum].argNode->gtOper == GT_ADDR); + assert(argNode->OperIsConst() || argNode->gtOper == GT_ADDR); } - noway_assert((inlArgInfo[argNum].argIsLclVar == 0) == - (inlArgInfo[argNum].argNode->gtOper != GT_LCL_VAR || - (inlArgInfo[argNum].argNode->gtFlags & GTF_GLOB_REF))); + noway_assert((argInfo.argIsLclVar == 0) == + (argNode->gtOper != GT_LCL_VAR || (argNode->gtFlags & GTF_GLOB_REF))); /* If the argument has side effects, append it */ - if (inlArgInfo[argNum].argHasSideEff) + if (argInfo.argHasSideEff) { - noway_assert(inlArgInfo[argNum].argIsUsed == false); + noway_assert(argInfo.argIsUsed == false); - if (inlArgInfo[argNum].argNode->gtOper == GT_OBJ || - inlArgInfo[argNum].argNode->gtOper == GT_MKREFANY) + if (argNode->gtOper == GT_OBJ || argNode->gtOper == GT_MKREFANY) { // Don't put GT_OBJ node under a GT_COMMA. // Codegen can't deal with it. // Just hang the address here in case there are side-effect. - newStmt = gtNewStmt(gtUnusedValNode(inlArgInfo[argNum].argNode->gtOp.gtOp1), callILOffset); + newStmt = gtNewStmt(gtUnusedValNode(argNode->gtOp.gtOp1), callILOffset); } else { - newStmt = gtNewStmt(gtUnusedValNode(inlArgInfo[argNum].argNode), callILOffset); + newStmt = gtNewStmt(gtUnusedValNode(argNode), callILOffset); } afterStmt = fgInsertStmtAfter(block, afterStmt, newStmt); diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp index c39004eabe45..599abf956226 100644 --- a/src/jit/importer.cpp +++ b/src/jit/importer.cpp @@ -2175,9 +2175,12 @@ bool Compiler::impSpillStackEntry(unsigned level, } } + bool isNewTemp = false; + if (tnum == BAD_VAR_NUM) { - tnum = lvaGrabTemp(true DEBUGARG(reason)); + tnum = lvaGrabTemp(true DEBUGARG(reason)); + isNewTemp = true; } else if (tiVerificationNeeded && lvaTable[tnum].TypeGet() != TYP_UNDEF) { @@ -2207,6 +2210,13 @@ bool Compiler::impSpillStackEntry(unsigned level, /* Assign the spilled entry to the temp */ impAssignTempGen(tnum, tree, verCurrentState.esStack[level].seTypeInfo.GetClassHandle(), level); + // If temp is newly introduced and a ref type, grab what type info we can. + if (isNewTemp && (lvaTable[tnum].lvType == TYP_REF)) + { + CORINFO_CLASS_HANDLE stkHnd = verCurrentState.esStack[level].seTypeInfo.GetClassHandle(); + lvaSetClass(tnum, tree, stkHnd); + } + // The tree type may be modified by impAssignTempGen, so use the type of the lclVar. var_types type = genActualType(lvaTable[tnum].TypeGet()); GenTreePtr temp = gtNewLclvNode(tnum, type); @@ -9410,9 +9420,10 @@ GenTreePtr Compiler::impCastClassOrIsInstToTree(GenTreePtr op1, // Make QMark node a top level node by spilling it. unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling QMark2")); - // TODO: Is it possible op1 has a better type? - lvaTable[tmp].lvClassHnd = pResolvedToken->hClass; impAssignTempGen(tmp, qmarkNull, (unsigned)CHECK_SPILL_NONE); + + // TODO: Is it possible op1 has a better type? + lvaSetClass(tmp, pResolvedToken->hClass); return gtNewLclvNode(tmp, TYP_REF); #endif } @@ -9670,6 +9681,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) GenTreeArgList* args = nullptr; // What good do these "DUMMY_INIT"s do? GenTreePtr newObjThisPtr = DUMMY_INIT(NULL); bool uns = DUMMY_INIT(false); + bool isLocal = false; /* Get the next opcode and the size of its parameters */ @@ -9925,7 +9937,9 @@ void Compiler::impImportBlockCode(BasicBlock* block) { lclNum = lvaArg0Var; } - lvaTable[lclNum].lvArgWrite = 1; + + // We should have seen this arg write in the prescan + assert(lvaTable[lclNum].lvHasILStoreOp); if (tiVerificationNeeded) { @@ -9942,12 +9956,14 @@ void Compiler::impImportBlockCode(BasicBlock* block) goto VAR_ST; case CEE_STLOC: - lclNum = getU2LittleEndian(codeAddr); + lclNum = getU2LittleEndian(codeAddr); + isLocal = true; JITDUMP(" %u", lclNum); goto LOC_ST; case CEE_STLOC_S: - lclNum = getU1LittleEndian(codeAddr); + lclNum = getU1LittleEndian(codeAddr); + isLocal = true; JITDUMP(" %u", lclNum); goto LOC_ST; @@ -9955,7 +9971,8 @@ void Compiler::impImportBlockCode(BasicBlock* block) case CEE_STLOC_1: case CEE_STLOC_2: case CEE_STLOC_3: - lclNum = (opcode - CEE_STLOC_0); + isLocal = true; + lclNum = (opcode - CEE_STLOC_0); assert(lclNum >= 0 && lclNum < 4); LOC_ST: @@ -10056,6 +10073,23 @@ void Compiler::impImportBlockCode(BasicBlock* block) } } + // If this is a local and the local is a ref type, see + // if we can improve type information based on the + // value being assigned. + if (isLocal && (lclTyp == TYP_REF)) + { + // We should have seen a stloc in our IL prescan. + assert(lvaTable[lclNum].lvHasILStoreOp); + + const bool isSingleILStoreLocal = + !lvaTable[lclNum].lvHasMultipleILStoreOp && !lvaTable[lclNum].lvHasLdAddrOp; + + if (isSingleILStoreLocal) + { + lvaUpdateClass(lclNum, op1, clsHnd); + } + } + /* Filter out simple assignments to itself */ if (op1->gtOper == GT_LCL_VAR && lclNum == op1->gtLclVarCommon.gtLclNum) @@ -11929,6 +11963,12 @@ void Compiler::impImportBlockCode(BasicBlock* block) impAssignTempGen(tmpNum, op1, tiRetVal.GetClassHandle(), (unsigned)CHECK_SPILL_ALL); var_types type = genActualType(lvaTable[tmpNum].TypeGet()); op1 = gtNewLclvNode(tmpNum, type); + + // Propagate type info to the temp + if (type == TYP_REF) + { + lvaSetClass(tmpNum, op1, tiRetVal.GetClassHandle()); + } } op1 = impCloneExpr(op1, &op2, tiRetVal.GetClassHandle(), (unsigned)CHECK_SPILL_ALL, @@ -12517,10 +12557,6 @@ void Compiler::impImportBlockCode(BasicBlock* block) /* get a temporary for the new object */ lclNum = lvaGrabTemp(true DEBUGARG("NewObj constructor temp")); - // Note the class handle... - lvaTable[lclNum].lvClassHnd = resolvedToken.hClass; - lvaTable[lclNum].lvClassIsExact = 1; - // In the value class case we only need clsHnd for size calcs. // // The lookup of the code pointer will be handled by CALL in this case @@ -12626,6 +12662,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) // without exhaustive walk over all expressions. impAssignTempGen(lclNum, op1, (unsigned)CHECK_SPILL_NONE); + lvaSetClass(lclNum, resolvedToken.hClass, true /* is Exact */); newObjThisPtr = gtNewLclvNode(lclNum, TYP_REF); } @@ -13587,9 +13624,10 @@ void Compiler::impImportBlockCode(BasicBlock* block) Verify(elemTypeHnd == nullptr || !(info.compCompHnd->getClassAttribs(elemTypeHnd) & CORINFO_FLG_CONTAINS_STACK_PTR), "array of byref-like type"); - tiRetVal = verMakeTypeInfo(resolvedToken.hClass); } + tiRetVal = verMakeTypeInfo(resolvedToken.hClass); + accessAllowedResult = info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper); impHandleAccessAllowed(accessAllowedResult, &calloutHelper); @@ -17706,15 +17744,18 @@ unsigned Compiler::impInlineFetchLocal(unsigned lclNum DEBUGARG(const char* reas impInlineInfo->lclTmpNum[lclNum] = tmpNum = lvaGrabTemp(false DEBUGARG(reason)); // Copy over key info - lvaTable[tmpNum].lvType = lclTyp; - lvaTable[tmpNum].lvHasLdAddrOp = inlineeLocal.lclHasLdlocaOp; - lvaTable[tmpNum].lvPinned = inlineeLocal.lclIsPinned; - - // Copy over class handle for ref types. Note this may be - // further improved if it is a shared type. + lvaTable[tmpNum].lvType = lclTyp; + lvaTable[tmpNum].lvHasLdAddrOp = inlineeLocal.lclHasLdlocaOp; + lvaTable[tmpNum].lvPinned = inlineeLocal.lclIsPinned; + lvaTable[tmpNum].lvHasILStoreOp = inlineeLocal.lclHasStlocOp; + lvaTable[tmpNum].lvHasMultipleILStoreOp = inlineeLocal.lclHasMultipleStlocOp; + + // Copy over class handle for ref types. Note this may be a + // shared type -- someday perhaps we can get the exact + // signature and pass in a more precise type. if (lclTyp == TYP_REF) { - lvaTable[tmpNum].lvClassHnd = inlineeLocal.lclVerTypeInfo.GetClassHandleForObjRef(); + lvaSetClass(tmpNum, inlineeLocal.lclVerTypeInfo.GetClassHandleForObjRef()); } if (inlineeLocal.lclVerTypeInfo.IsStruct()) @@ -17886,7 +17927,7 @@ GenTreePtr Compiler::impInlineFetchArg(unsigned lclNum, InlArgInfo* inlArgInfo, // further improved if it is a shared type and we know the exact context. if (lclTyp == TYP_REF) { - lvaTable[tmpNum].lvClassHnd = lclInfo.lclVerTypeInfo.GetClassHandleForObjRef(); + lvaSetClass(tmpNum, lclInfo.lclVerTypeInfo.GetClassHandleForObjRef()); } assert(lvaTable[tmpNum].lvAddrExposed == 0); diff --git a/src/jit/inline.h b/src/jit/inline.h index bf4d8046b6b7..ee07130676fb 100644 --- a/src/jit/inline.h +++ b/src/jit/inline.h @@ -514,31 +514,32 @@ struct InlineCandidateInfo struct InlArgInfo { - unsigned argIsUsed : 1; // is this arg used at all? - unsigned argIsInvariant : 1; // the argument is a constant or a local variable address - unsigned argIsLclVar : 1; // the argument is a local variable - unsigned argIsThis : 1; // the argument is the 'this' pointer - unsigned argHasSideEff : 1; // the argument has side effects - unsigned argHasGlobRef : 1; // the argument has a global ref - unsigned argHasTmp : 1; // the argument will be evaluated to a temp - unsigned argIsByRefToStructLocal : 1; // Is this arg an address of a struct local or a normed struct local or a - // field in them? - unsigned argHasLdargaOp : 1; // Is there LDARGA(s) operation on this argument? - unsigned argHasStargOp : 1; // Is there STARG(s) operation on this argument? - - unsigned argTmpNum; // the argument tmp number - GenTreePtr argNode; - GenTreePtr argBashTmpNode; // tmp node created, if it may be replaced with actual arg + GenTreePtr argNode; // caller node for this argument + GenTreePtr argBashTmpNode; // tmp node created, if it may be replaced with actual arg + unsigned argTmpNum; // the argument tmp number + unsigned argIsUsed : 1; // is this arg used at all? + unsigned argIsInvariant : 1; // the argument is a constant or a local variable address + unsigned argIsLclVar : 1; // the argument is a local variable + unsigned argIsThis : 1; // the argument is the 'this' pointer + unsigned argHasSideEff : 1; // the argument has side effects + unsigned argHasGlobRef : 1; // the argument has a global ref + unsigned argHasTmp : 1; // the argument will be evaluated to a temp + unsigned argHasLdargaOp : 1; // Is there LDARGA(s) operation on this argument? + unsigned argHasStargOp : 1; // Is there STARG(s) operation on this argument? + unsigned argIsByRefToStructLocal : 1; // Is this arg an address of a struct local or a normed struct local or a + // field in them? }; -// InlArgInfo describes inline candidate local variable properties. +// InlLclVarInfo describes inline candidate argument and local variable properties. struct InlLclVarInfo { - var_types lclTypeInfo; typeInfo lclVerTypeInfo; - bool lclHasLdlocaOp; // Is there LDLOCA(s) operation on this argument? - bool lclIsPinned; + var_types lclTypeInfo; + unsigned lclHasLdlocaOp : 1; // Is there LDLOCA(s) operation on this local? + unsigned lclHasStlocOp : 1; // Is there a STLOC on this local? + unsigned lclHasMultipleStlocOp : 1; // Is there more than one STLOC on this local + unsigned lclIsPinned : 1; }; // InlineInfo provides detailed information about a particular inline candidate. diff --git a/src/jit/lclvars.cpp b/src/jit/lclvars.cpp index 319a64d92172..854372e0a349 100644 --- a/src/jit/lclvars.cpp +++ b/src/jit/lclvars.cpp @@ -257,7 +257,8 @@ void Compiler::lvaInitTypeRef() if (strip(corInfoType) == CORINFO_TYPE_CLASS) { - varDsc->lvClassHnd = info.compCompHnd->getArgClass(&info.compMethodInfo->locals, localsSig); + CORINFO_CLASS_HANDLE clsHnd = info.compCompHnd->getArgClass(&info.compMethodInfo->locals, localsSig); + lvaSetClass(varNum, clsHnd); } } @@ -404,6 +405,7 @@ void Compiler::lvaInitThisPtr(InitVarDscInfo* varDscInfo) else { varDsc->lvType = TYP_REF; + lvaSetClass(varDscInfo->varNum, info.compClassHnd); } if (tiVerificationNeeded) @@ -420,8 +422,6 @@ void Compiler::lvaInitThisPtr(InitVarDscInfo* varDscInfo) varDsc->lvVerTypeInfo = typeInfo(); } - varDsc->lvClassHnd = info.compClassHnd; - // Mark the 'this' pointer for the method varDsc->lvVerTypeInfo.SetIsThisPtr(); @@ -562,7 +562,8 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo) if (strip(corInfoType) == CORINFO_TYPE_CLASS) { - varDsc->lvClassHnd = info.compCompHnd->getArgClass(&info.compMethodInfo->args, argLst); + CORINFO_CLASS_HANDLE clsHnd = info.compCompHnd->getArgClass(&info.compMethodInfo->args, argLst); + lvaSetClass(varDscInfo->varNum, clsHnd); } // For ARM, ARM64, and AMD64 varargs, all arguments go in integer registers @@ -2297,6 +2298,171 @@ void Compiler::lvaSetStruct(unsigned varNum, CORINFO_CLASS_HANDLE typeHnd, bool } } +//------------------------------------------------------------------------ +// lvaSetClass: set class information for a local var. +// +// Arguments: +// varNum -- number of the variable +// clsHnd -- class handle to use in set or update +// isExact -- true if class is known exactly +// +// Notes: +// varNum must not already have a ref class handle. + +void Compiler::lvaSetClass(unsigned varNum, CORINFO_CLASS_HANDLE clsHnd, bool isExact) +{ + noway_assert(varNum < lvaCount); + assert(clsHnd != nullptr); + + LclVarDsc* varDsc = &lvaTable[varNum]; + assert(varDsc->lvType == TYP_REF); + + // We shoud not have any ref type information for this var. + assert(varDsc->lvClassHnd == nullptr); + assert(!varDsc->lvClassIsExact); + + JITDUMP("\nlvaSetClass: setting class for V%02i to (%p) %s %s\n", varNum, clsHnd, + info.compCompHnd->getClassName(clsHnd), isExact ? " [exact]" : ""); + + varDsc->lvClassHnd = clsHnd; + varDsc->lvClassIsExact = isExact; +} + +//------------------------------------------------------------------------ +// lvaSetClass: set class information for a local var from a tree or stack type +// +// Arguments: +// varNum -- number of the variable. Must be a single def local +// tree -- tree establishing the variable's value +// stackHnd -- handle for the type from the evaluation stack +// +// Notes: +// Preferentially uses the tree's type, when available. Since not all +// tree kinds can track ref types, the stack type is used as a +// fallback. + +void Compiler::lvaSetClass(unsigned varNum, GenTreePtr tree, CORINFO_CLASS_HANDLE stackHnd) +{ + bool isExact = false; + bool isNonNull = false; + CORINFO_CLASS_HANDLE clsHnd = gtGetClassHandle(tree, &isExact, &isNonNull); + + if (clsHnd != nullptr) + { + lvaSetClass(varNum, clsHnd, isExact); + } + else if (stackHnd != nullptr) + { + lvaSetClass(varNum, stackHnd); + } +} + +//------------------------------------------------------------------------ +// lvaUpdateClass: update class information for a local var. +// +// Arguments: +// varNum -- number of the variable +// clsHnd -- class handle to use in set or update +// isExact -- true if class is known exactly +// +// Notes: +// +// This method models the type update rule for an assignment. +// +// Updates currently should only happen for single-def user args or +// locals, when we are processing the expression actually being +// used to initialize the local (or inlined arg). The update will +// change the local from the declared type to the type of the +// initial value. +// +// These updates should always *improve* what we know about the +// type, that is making an inexact type exact, or changing a type +// to some subtype. However the jit lacks precise type information +// for shared code, so ensuring this is so is currently not +// possible. + +void Compiler::lvaUpdateClass(unsigned varNum, CORINFO_CLASS_HANDLE clsHnd, bool isExact) +{ + noway_assert(varNum < lvaCount); + assert(clsHnd != nullptr); + + LclVarDsc* varDsc = &lvaTable[varNum]; + assert(varDsc->lvType == TYP_REF); + + // We should already have a class + assert(varDsc->lvClassHnd != nullptr); + + // This should be the first and only update for this var + assert(!varDsc->lvClassInfoUpdated); + +#if defined(DEBUG) + // This counts as an update, even if nothing changes. + varDsc->lvClassInfoUpdated = true; +#endif // defined(DEBUG) + + // If previous type was exact, there is nothing to update. Would + // like to verify new type is compatible but can't do this yet. + if (varDsc->lvClassIsExact) + { + return; + } + + // Are we updating the type? + if (varDsc->lvClassHnd != clsHnd) + { + JITDUMP("\nlvaUpdateClass: Updating class for V%02i from (%p) %s to (%p) %s %s\n", varNum, varDsc->lvClassHnd, + info.compCompHnd->getClassName(varDsc->lvClassHnd), clsHnd, info.compCompHnd->getClassName(clsHnd), + isExact ? " [exact]" : ""); + + varDsc->lvClassHnd = clsHnd; + varDsc->lvClassIsExact = isExact; + return; + } + + // Class info matched. Are we updating exactness? + if (isExact) + { + JITDUMP("\nlvaUpdateClass: Updating class for V%02i (%p) %s to be exact\n", varNum, varDsc->lvClassHnd, + info.compCompHnd->getClassName(varDsc->lvClassHnd)); + + varDsc->lvClassIsExact = isExact; + return; + } + + // Else we have the same handle and (in)exactness as before. Do nothing. + return; +} + +//------------------------------------------------------------------------ +// lvaUpdateClass: Uupdate class information for a local var from a tree +// or stack type +// +// Arguments: +// varNum -- number of the variable. Must be a single def local +// tree -- tree establishing the variable's value +// stackHnd -- handle for the type from the evaluation stack +// +// Notes: +// Preferentially uses the tree's type, when available. Since not all +// tree kinds can track ref types, the stack type is used as a +// fallback. + +void Compiler::lvaUpdateClass(unsigned varNum, GenTreePtr tree, CORINFO_CLASS_HANDLE stackHnd) +{ + bool isExact = false; + bool isNonNull = false; + CORINFO_CLASS_HANDLE clsHnd = gtGetClassHandle(tree, &isExact, &isNonNull); + + if (clsHnd != nullptr) + { + lvaUpdateClass(varNum, clsHnd, isExact); + } + else if (stackHnd != nullptr) + { + lvaUpdateClass(varNum, stackHnd); + } +} + /***************************************************************************** * Returns the array of BYTEs containing the GC layout information */ @@ -6491,6 +6657,14 @@ void Compiler::lvaDumpEntry(unsigned lclNum, FrameLayoutState curState, size_t r { printf(" stack-byref"); } + if (varDsc->lvClassHnd != nullptr) + { + printf(" class-hnd"); + } + if (varDsc->lvClassIsExact) + { + printf(" exact"); + } #ifndef _TARGET_64BIT_ if (varDsc->lvStructDoubleAlign) printf(" double-align");