void CodeGen::siBeginBlock(BasicBlock* block) { assert(block != nullptr); if (!compiler->opts.compScopeInfo) return; if (compiler->info.compVarScopesCount == 0) return; #if FEATURE_EH_FUNCLETS if (siInFuncletRegion) return; if (block->bbFlags & BBF_FUNCLET_BEG) { // For now, don't report any scopes in funclets. JIT64 doesn't. siInFuncletRegion = true; JITDUMP("Scope info: found beginning of funclet region at block BB%02u; ignoring following blocks\n", block->bbNum); return; } #endif // FEATURE_EH_FUNCLETS #ifdef DEBUG if (verbose) { printf("\nScope info: begin block BB%02u, IL range ", block->bbNum); block->dspBlockILRange(); printf("\n"); } #endif // DEBUG unsigned beginOffs = block->bbCodeOffs; if (beginOffs == BAD_IL_OFFSET) { JITDUMP("Scope info: ignoring block beginning\n"); return; } if (!compiler->opts.compDbgCode) { /* For non-debuggable code */ // End scope of variables which are not live for this block siUpdate(); // Check that vars which are live on entry have an open scope VARSET_ITER_INIT(compiler, iter, block->bbLiveIn, i); while (iter.NextElem(compiler, &i)) { unsigned varNum = compiler->lvaTrackedToVarNum[i]; // lvRefCnt may go down to 0 after liveness-analysis. // So we need to check if this tracked variable is actually used. if (!compiler->lvaTable[varNum].lvIsInReg() && !compiler->lvaTable[varNum].lvOnFrame) { assert(compiler->lvaTable[varNum].lvRefCnt == 0); continue; } siCheckVarScope(varNum, beginOffs); } } else { // For debuggable code, scopes can begin only on block boundaries. // Check if there are any scopes on the current block's start boundary. VarScopeDsc* varScope; #if FEATURE_EH_FUNCLETS // If we find a spot where the code offset isn't what we expect, because // there is a gap, it might be because we've moved the funclets out of // line. Catch up with the enter and exit scopes of the current block. // Ignore the enter/exit scope changes of the missing scopes, which for // funclets must be matched. if (siLastEndOffs != beginOffs) { assert(beginOffs > 0); assert(siLastEndOffs < beginOffs); JITDUMP("Scope info: found offset hole. lastOffs=%u, currOffs=%u\n", siLastEndOffs, beginOffs); // Skip enter scopes while ((varScope = compiler->compGetNextEnterScope(beginOffs - 1, true)) != NULL) { /* do nothing */ JITDUMP("Scope info: skipping enter scope, LVnum=%u\n", varScope->vsdLVnum); } // Skip exit scopes while ((varScope = compiler->compGetNextExitScope(beginOffs - 1, true)) != NULL) { /* do nothing */ JITDUMP("Scope info: skipping exit scope, LVnum=%u\n", varScope->vsdLVnum); } } #else // FEATURE_EH_FUNCLETS if (siLastEndOffs != beginOffs) { assert(siLastEndOffs < beginOffs); return; } #endif // FEATURE_EH_FUNCLETS while ((varScope = compiler->compGetNextEnterScope(beginOffs)) != NULL) { // brace-matching editor workaround for following line: ( JITDUMP("Scope info: opening scope, LVnum=%u [%03X..%03X)\n", varScope->vsdLVnum, varScope->vsdLifeBeg, varScope->vsdLifeEnd); siNewScope(varScope->vsdLVnum, varScope->vsdVarNum); #ifdef DEBUG LclVarDsc* lclVarDsc1 = &compiler->lvaTable[varScope->vsdVarNum]; if (VERBOSE) { printf("Scope info: >> new scope, VarNum=%u, tracked? %s, VarIndex=%u, bbLiveIn=%s ", varScope->vsdVarNum, lclVarDsc1->lvTracked ? "yes" : "no", lclVarDsc1->lvVarIndex, VarSetOps::ToString(compiler, block->bbLiveIn)); dumpConvertedVarSet(compiler, block->bbLiveIn); printf("\n"); } assert(!lclVarDsc1->lvTracked || VarSetOps::IsMember(compiler, block->bbLiveIn, lclVarDsc1->lvVarIndex)); #endif // DEBUG } } #ifdef DEBUG if (verbose) siDispOpenScopes(); #endif }
void TreeLifeUpdater<ForCodeGen>::UpdateLifeVar(GenTree* tree) { GenTree* indirAddrLocal = compiler->fgIsIndirOfAddrOfLocal(tree); assert(tree->OperIsNonPhiLocal() || indirAddrLocal != nullptr); // Get the local var tree -- if "tree" is "Ldobj(addr(x))", or "ind(addr(x))" this is "x", else it's "tree". GenTree* lclVarTree = indirAddrLocal; if (lclVarTree == nullptr) { lclVarTree = tree; } unsigned int lclNum = lclVarTree->gtLclVarCommon.gtLclNum; LclVarDsc* varDsc = compiler->lvaTable + lclNum; #ifdef DEBUG #if !defined(_TARGET_AMD64_) // There are no addr nodes on ARM and we are experimenting with encountering vars in 'random' order. // Struct fields are not traversed in a consistent order, so ignore them when // verifying that we see the var nodes in execution order if (ForCodeGen) { if (tree->OperIsIndir()) { assert(indirAddrLocal != NULL); } else if (tree->gtNext != NULL && tree->gtNext->gtOper == GT_ADDR && ((tree->gtNext->gtNext == NULL || !tree->gtNext->gtNext->OperIsIndir()))) { assert(tree->IsLocal()); // Can only take the address of a local. // The ADDR might occur in a context where the address it contributes is eventually // dereferenced, so we can't say that this is not a use or def. } } #endif // !_TARGET_AMD64_ #endif // DEBUG compiler->compCurLifeTree = tree; VarSetOps::Assign(compiler, newLife, compiler->compCurLife); // By codegen, a struct may not be TYP_STRUCT, so we have to // check lvPromoted, for the case where the fields are being // tracked. if (!varDsc->lvTracked && !varDsc->lvPromoted) { return; } // if it's a partial definition then variable "x" must have had a previous, original, site to be born. bool isBorn = ((tree->gtFlags & GTF_VAR_DEF) != 0 && (tree->gtFlags & GTF_VAR_USEASG) == 0); bool isDying = ((tree->gtFlags & GTF_VAR_DEATH) != 0); bool spill = ((tree->gtFlags & GTF_SPILL) != 0); // Since all tracked vars are register candidates, but not all are in registers at all times, // we maintain two separate sets of variables - the total set of variables that are either // born or dying here, and the subset of those that are on the stack VarSetOps::ClearD(compiler, stackVarDeltaSet); if (isBorn || isDying) { VarSetOps::ClearD(compiler, varDeltaSet); if (varDsc->lvTracked) { VarSetOps::AddElemD(compiler, varDeltaSet, varDsc->lvVarIndex); if (ForCodeGen) { if (isBorn && varDsc->lvIsRegCandidate() && tree->gtHasReg()) { compiler->codeGen->genUpdateVarReg(varDsc, tree); } if (varDsc->lvIsInReg() && tree->gtRegNum != REG_NA) { compiler->codeGen->genUpdateRegLife(varDsc, isBorn, isDying DEBUGARG(tree)); } else { VarSetOps::AddElemD(compiler, stackVarDeltaSet, varDsc->lvVarIndex); } } } else if (varDsc->lvPromoted) { // If hasDeadTrackedFieldVars is true, then, for a LDOBJ(ADDR(<promoted struct local>)), // *deadTrackedFieldVars indicates which tracked field vars are dying. bool hasDeadTrackedFieldVars = false; if (indirAddrLocal != nullptr && isDying) { assert(!isBorn); // GTF_VAR_DEATH only set for LDOBJ last use. VARSET_TP* deadTrackedFieldVars = nullptr; hasDeadTrackedFieldVars = compiler->LookupPromotedStructDeathVars(indirAddrLocal, &deadTrackedFieldVars); if (hasDeadTrackedFieldVars) { VarSetOps::Assign(compiler, varDeltaSet, *deadTrackedFieldVars); } } for (unsigned i = varDsc->lvFieldLclStart; i < varDsc->lvFieldLclStart + varDsc->lvFieldCnt; ++i) { LclVarDsc* fldVarDsc = &(compiler->lvaTable[i]); noway_assert(fldVarDsc->lvIsStructField); if (fldVarDsc->lvTracked) { unsigned fldVarIndex = fldVarDsc->lvVarIndex; noway_assert(fldVarIndex < compiler->lvaTrackedCount); if (!hasDeadTrackedFieldVars) { VarSetOps::AddElemD(compiler, varDeltaSet, fldVarIndex); if (ForCodeGen) { // We repeat this call here and below to avoid the VarSetOps::IsMember // test in this, the common case, where we have no deadTrackedFieldVars. if (fldVarDsc->lvIsInReg()) { if (isBorn) { compiler->codeGen->genUpdateVarReg(fldVarDsc, tree); } compiler->codeGen->genUpdateRegLife(fldVarDsc, isBorn, isDying DEBUGARG(tree)); } else { VarSetOps::AddElemD(compiler, stackVarDeltaSet, fldVarIndex); } } } else if (ForCodeGen && VarSetOps::IsMember(compiler, varDeltaSet, fldVarIndex)) { if (compiler->lvaTable[i].lvIsInReg()) { if (isBorn) { compiler->codeGen->genUpdateVarReg(fldVarDsc, tree); } compiler->codeGen->genUpdateRegLife(fldVarDsc, isBorn, isDying DEBUGARG(tree)); } else { VarSetOps::AddElemD(compiler, stackVarDeltaSet, fldVarIndex); } } } } } // First, update the live set if (isDying) { // We'd like to be able to assert the following, however if we are walking // through a qmark/colon tree, we may encounter multiple last-use nodes. // assert (VarSetOps::IsSubset(compiler, regVarDeltaSet, newLife)); VarSetOps::DiffD(compiler, newLife, varDeltaSet); } else { // This shouldn't be in newLife, unless this is debug code, in which // case we keep vars live everywhere, OR the variable is address-exposed, // OR this block is part of a try block, in which case it may be live at the handler // Could add a check that, if it's in newLife, that it's also in // fgGetHandlerLiveVars(compCurBB), but seems excessive // // For a dead store, it can be the case that we set both isBorn and isDying to true. // (We don't eliminate dead stores under MinOpts, so we can't assume they're always // eliminated.) If it's both, we handled it above. VarSetOps::UnionD(compiler, newLife, varDeltaSet); } } if (!VarSetOps::Equal(compiler, compiler->compCurLife, newLife)) { #ifdef DEBUG if (compiler->verbose) { printf("\t\t\t\t\t\t\tLive vars: "); dumpConvertedVarSet(compiler, compiler->compCurLife); printf(" => "); dumpConvertedVarSet(compiler, newLife); printf("\n"); } #endif // DEBUG VarSetOps::Assign(compiler, compiler->compCurLife, newLife); if (ForCodeGen) { // Only add vars to the gcInfo.gcVarPtrSetCur if they are currently on stack, since the // gcInfo.gcTrkStkPtrLcls // includes all TRACKED vars that EVER live on the stack (i.e. are not always in a register). VarSetOps::Assign(compiler, gcTrkStkDeltaSet, compiler->codeGen->gcInfo.gcTrkStkPtrLcls); VarSetOps::IntersectionD(compiler, gcTrkStkDeltaSet, stackVarDeltaSet); if (!VarSetOps::IsEmpty(compiler, gcTrkStkDeltaSet)) { #ifdef DEBUG if (compiler->verbose) { printf("\t\t\t\t\t\t\tGCvars: "); dumpConvertedVarSet(compiler, compiler->codeGen->gcInfo.gcVarPtrSetCur); printf(" => "); } #endif // DEBUG if (isBorn) { VarSetOps::UnionD(compiler, compiler->codeGen->gcInfo.gcVarPtrSetCur, gcTrkStkDeltaSet); } else { VarSetOps::DiffD(compiler, compiler->codeGen->gcInfo.gcVarPtrSetCur, gcTrkStkDeltaSet); } #ifdef DEBUG if (compiler->verbose) { dumpConvertedVarSet(compiler, compiler->codeGen->gcInfo.gcVarPtrSetCur); printf("\n"); } #endif // DEBUG } #ifdef USING_SCOPE_INFO compiler->codeGen->siUpdate(); #endif // USING_SCOPE_INFO } } if (ForCodeGen && spill) { assert(!varDsc->lvPromoted); compiler->codeGen->genSpillVar(tree); if (VarSetOps::IsMember(compiler, compiler->codeGen->gcInfo.gcTrkStkPtrLcls, varDsc->lvVarIndex)) { if (!VarSetOps::IsMember(compiler, compiler->codeGen->gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex)) { VarSetOps::AddElemD(compiler, compiler->codeGen->gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex); #ifdef DEBUG if (compiler->verbose) { printf("\t\t\t\t\t\t\tVar V%02u becoming live\n", varDsc - compiler->lvaTable); } #endif // DEBUG } } } }