Skip to content

Commit

Permalink
JIT: introduce some SSA accounting and checking (#77055)
Browse files Browse the repository at this point in the history
Record some information about each SSA def and try and keep it conservatively
correct through the first few optimization phases.

We note:
* total number of uses
* whether all uses are in the same block as the def
* whether there are any phi uses

Subsequent phases that introduce new uses must now call `optRecordSsaUses` on
the new trees they create to update these accounts.

This information is cross-checked versus the IR in post phase checking.
Because we don't have a well-defined mechanism to track when nodes are deleted
the recorded counts may end up being overestimates in subsequent phases. This is
ok, but underestimates are flagged as errors.
  • Loading branch information
AndyAyersMS committed Nov 1, 2022
1 parent 5b290d3 commit 5ec2e87
Show file tree
Hide file tree
Showing 11 changed files with 822 additions and 87 deletions.
15 changes: 15 additions & 0 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1950,6 +1950,7 @@ void Compiler::compInit(ArenaAllocator* pAlloc,
m_outlinedCompositeSsaNums = nullptr;
m_nodeToLoopMemoryBlockMap = nullptr;
fgSsaPassesCompleted = 0;
fgSsaChecksEnabled = false;
fgVNPassesCompleted = 0;

// check that HelperCallProperties are initialized
Expand Down Expand Up @@ -4803,6 +4804,8 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl

if (doBranchOpt)
{
// Optimize redundant branches
//
DoPhase(this, PHASE_OPTIMIZE_BRANCHES, &Compiler::optRedundantBranches);
}

Expand All @@ -4813,6 +4816,17 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
DoPhase(this, PHASE_OPTIMIZE_VALNUM_CSES, &Compiler::optOptimizeCSEs);
}

// Assertion prop can do arbitrary statement remorphing, which
// can clone code and disrupt our simpleminded SSA accounting.
//
// So, disable the ssa checks.
//
if (fgSsaChecksEnabled)
{
JITDUMP("Disabling SSA checking before assertion prop\n");
fgSsaChecksEnabled = false;
}

if (doAssertionProp)
{
// Assertion propagation
Expand Down Expand Up @@ -5346,6 +5360,7 @@ void Compiler::ResetOptAnnotations()
m_blockToEHPreds = nullptr;
fgSsaPassesCompleted = 0;
fgVNPassesCompleted = 0;
fgSsaChecksEnabled = false;

for (BasicBlock* const block : Blocks())
{
Expand Down
58 changes: 56 additions & 2 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ class LclSsaVarDsc
GenTreeOp* m_asg = nullptr;
// The SSA number associated with the previous definition for partial (GTF_USEASG) defs.
unsigned m_useDefSsaNum = SsaConfig::RESERVED_SSA_NUM;
// Number of uses of this SSA def (may be an over-estimate). Includes phi args uses.
unsigned short m_numUses = 0;
// True if there may be phi args uses of this def
// (false implies all uses are non-phi)
bool m_hasPhiUse = false;
// True if there may be uses of the def in a different block
bool m_hasGlobalUse = false;

public:
LclSsaVarDsc()
Expand Down Expand Up @@ -263,6 +270,40 @@ class LclSsaVarDsc
m_useDefSsaNum = ssaNum;
}

unsigned GetNumUses() const
{
return m_numUses;
}

void AddUse(BasicBlock* block)
{
if (block != m_block)
{
m_hasGlobalUse = true;
}

if (m_numUses < USHRT_MAX)
{
m_numUses++;
}
}

void AddPhiUse(BasicBlock* block)
{
m_hasPhiUse = true;
AddUse(block);
}

bool HasPhiUse() const
{
return m_hasPhiUse;
}

bool HasGlobalUse() const
{
return m_hasGlobalUse;
}

ValueNumPair m_vnPair;
};

Expand Down Expand Up @@ -373,7 +414,7 @@ class SsaDefArray
}

// Get an SSA number associated with the specified SSA def (that must be in this array).
unsigned GetSsaNum(T* ssaDef)
unsigned GetSsaNum(T* ssaDef) const
{
assert((m_array <= ssaDef) && (ssaDef < &m_array[m_count]));
return GetMinSsaNum() + static_cast<unsigned>(ssaDef - &m_array[0]);
Expand Down Expand Up @@ -4733,6 +4774,11 @@ class Compiler
void fgResetForSsa();

unsigned fgSsaPassesCompleted; // Number of times fgSsaBuild has been run.
bool fgSsaChecksEnabled; // True if SSA info can be cross-checked versus IR

#ifdef DEBUG
void DumpSsaSummary();
#endif

// Returns "true" if this is a special variable that is never zero initialized in the prolog.
inline bool fgVarIsNeverZeroInitializedInProlog(unsigned varNum);
Expand Down Expand Up @@ -5346,6 +5392,7 @@ class Compiler
void fgDebugCheckNodeLinks(BasicBlock* block, Statement* stmt);
void fgDebugCheckNodesUniqueness();
void fgDebugCheckLoopTable();
void fgDebugCheckSsa();

void fgDebugCheckFlags(GenTree* tree);
void fgDebugCheckDispFlags(GenTree* tree, GenTreeFlags dispFlags, GenTreeDebugFlags debugFlags);
Expand Down Expand Up @@ -5975,6 +6022,9 @@ class Compiler
// Performs the hoisting 'tree' into the PreHeader for loop 'lnum'
void optHoistCandidate(GenTree* tree, BasicBlock* treeBb, unsigned lnum, LoopHoistContext* hoistCtxt);

// Note the new SSA uses in tree
void optRecordSsaUses(GenTree* tree, BasicBlock* block);

// Returns true iff the ValueNum "vn" represents a value that is loop-invariant in "lnum".
// Constants and init values are always loop invariant.
// VNPhi's connect VN's to the SSA definition, so we can know if the SSA def occurs in the loop.
Expand Down Expand Up @@ -6736,7 +6786,11 @@ class Compiler
typedef JitHashTable<unsigned, JitSmallPrimitiveKeyFuncs<unsigned>, CopyPropSsaDefStack*> LclNumToLiveDefsMap;

// Copy propagation functions.
bool optCopyProp(Statement* stmt, GenTreeLclVarCommon* tree, unsigned lclNum, LclNumToLiveDefsMap* curSsaName);
bool optCopyProp(BasicBlock* block,
Statement* stmt,
GenTreeLclVarCommon* tree,
unsigned lclNum,
LclNumToLiveDefsMap* curSsaName);
void optBlockCopyPropPopStacks(BasicBlock* block, LclNumToLiveDefsMap* curSsaName);
bool optBlockCopyProp(BasicBlock* block, LclNumToLiveDefsMap* curSsaName);
void optCopyPropPushDef(GenTree* defNode, GenTreeLclVarCommon* lclNode, LclNumToLiveDefsMap* curSsaName);
Expand Down
13 changes: 8 additions & 5 deletions src/coreclr/jit/copyprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ int Compiler::optCopyProp_LclVarScore(const LclVarDsc* lclVarDsc, const LclVarDs
// definitions share the same value number. If so, then we can make the replacement.
//
// Arguments:
// block - BasicBlock containing stmt
// stmt - Statement the tree belongs to
// tree - The local tree to perform copy propagation on
// lclNum - Number of the local "tree" refers to
Expand All @@ -154,7 +155,8 @@ int Compiler::optCopyProp_LclVarScore(const LclVarDsc* lclVarDsc, const LclVarDs
// Returns:
// Whether any changes were made.
//
bool Compiler::optCopyProp(Statement* stmt, GenTreeLclVarCommon* tree, unsigned lclNum, LclNumToLiveDefsMap* curSsaName)
bool Compiler::optCopyProp(
BasicBlock* block, Statement* stmt, GenTreeLclVarCommon* tree, unsigned lclNum, LclNumToLiveDefsMap* curSsaName)
{
assert(((tree->gtFlags & GTF_VAR_DEF) == 0) && (tree->GetLclNum() == lclNum) && tree->gtVNPair.BothDefined());

Expand All @@ -173,8 +175,8 @@ bool Compiler::optCopyProp(Statement* stmt, GenTreeLclVarCommon* tree, unsigned
continue;
}

CopyPropSsaDef newLclDef = iter.GetValue()->Top();
LclSsaVarDsc* newLclSsaDef = newLclDef.GetSsaDef();
CopyPropSsaDef newLclDef = iter.GetValue()->Top();
LclSsaVarDsc* const newLclSsaDef = newLclDef.GetSsaDef();

// Likewise, nothing to do if the most recent def is not available.
if (newLclSsaDef == nullptr)
Expand All @@ -195,7 +197,7 @@ bool Compiler::optCopyProp(Statement* stmt, GenTreeLclVarCommon* tree, unsigned
// is not marked 'lvDoNotEnregister'.
// However, in addition, it may not be profitable to propagate a 'doNotEnregister' lclVar to an
// existing use of an enregisterable lclVar.
LclVarDsc* newLclVarDsc = lvaGetDesc(newLclNum);
LclVarDsc* const newLclVarDsc = lvaGetDesc(newLclNum);
if (varDsc->lvDoNotEnregister != newLclVarDsc->lvDoNotEnregister)
{
continue;
Expand Down Expand Up @@ -262,6 +264,7 @@ bool Compiler::optCopyProp(Statement* stmt, GenTreeLclVarCommon* tree, unsigned
tree->AsLclVarCommon()->SetLclNum(newLclNum);
tree->AsLclVarCommon()->SetSsaNum(newSsaNum);
gtUpdateSideEffects(stmt, tree);
newLclSsaDef->AddUse(block);

#ifdef DEBUG
if (verbose)
Expand Down Expand Up @@ -415,7 +418,7 @@ bool Compiler::optBlockCopyProp(BasicBlock* block, LclNumToLiveDefsMap* curSsaNa
continue;
}

madeChanges |= optCopyProp(stmt, tree->AsLclVarCommon(), lclNum, curSsaName);
madeChanges |= optCopyProp(block, stmt, tree->AsLclVarCommon(), lclNum, curSsaName);
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/jit/earlyprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,9 @@ GenTree* Compiler::optEarlyPropRewriteTree(GenTree* tree, LocalNumberToNullCheck
// actualValClone has small tree node size, it is safe to use CopyFrom here.
tree->ReplaceWith(actualValClone, this);

// update SSA accounting
optRecordSsaUses(tree, compCurBB);

// Propagating a constant may create an opportunity to use a division by constant optimization
//
if ((tree->gtNext != nullptr) && tree->gtNext->OperIsBinary())
Expand Down Expand Up @@ -468,6 +471,7 @@ bool Compiler::optFoldNullCheck(GenTree* tree, LocalNumberToNullCheckTreeMap* nu
// Re-morph the statement.
Statement* curStmt = compCurStmt;
fgMorphBlockStmt(compCurBB, nullCheckStmt DEBUGARG("optFoldNullCheck"));
optRecordSsaUses(nullCheckStmt->GetRootNode(), compCurBB);
compCurStmt = curStmt;

folded = true;
Expand Down
Loading

0 comments on commit 5ec2e87

Please sign in to comment.