Skip to content

Commit

Permalink
[DebugInfo][InstrRef][NFC] Use depth-first scope search for variable …
Browse files Browse the repository at this point in the history
…locs

This patch aims to reduce max-rss from instruction referencing, by avoiding
keeping variable value information in memory for too long. Instead of
computing all the variable values then emitting them to DBG_VALUE
instructions, this patch tries to stream the information out through a
depth first search:
 * Make use of the fact LexicalScopes gives a depth-number to each lexical
   scope,
 * Produce a map that identifies the last lexical scope to make use of a
   block,
 * Enumerate each scope in LexicalScopes' DFS order, solving the variable
   value problem,
 * After each scope is processed, look for any blocks that won't be used by
   any other scope, and emit all the variable information to DBG_VALUE
   instructions.

Differential Revision: https://reviews.llvm.org/D118460
  • Loading branch information
jmorse committed Feb 2, 2022
1 parent 0cd8063 commit 9fd9d56
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 72 deletions.
234 changes: 174 additions & 60 deletions llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2821,45 +2821,6 @@ void InstrRefBasedLDV::dump_mloc_transfer(
}
#endif

void InstrRefBasedLDV::emitLocations(
MachineFunction &MF, LiveInsT SavedLiveIns, ValueIDNum **MOutLocs,
ValueIDNum **MInLocs, DenseMap<DebugVariable, unsigned> &AllVarsNumbering,
const TargetPassConfig &TPC) {
TTracker = new TransferTracker(TII, MTracker, MF, *TRI, CalleeSavedRegs, TPC);
unsigned NumLocs = MTracker->getNumLocs();
VTracker = nullptr;

// For each block, load in the machine value locations and variable value
// live-ins, then step through each instruction in the block. New DBG_VALUEs
// to be inserted will be created along the way.
for (MachineBasicBlock &MBB : MF) {
unsigned bbnum = MBB.getNumber();
MTracker->reset();
MTracker->loadFromArray(MInLocs[bbnum], bbnum);
TTracker->loadInlocs(MBB, MInLocs[bbnum], SavedLiveIns[MBB.getNumber()],
NumLocs);

CurBB = bbnum;
CurInst = 1;
for (auto &MI : MBB) {
process(MI, MOutLocs, MInLocs);
TTracker->checkInstForNewValues(CurInst, MI.getIterator());
++CurInst;
}

// Our block information has now been converted into DBG_VALUEs, to be
// inserted below. Free the memory we allocated to track variable / register
// values. If we don't, we needlessy record the same info in memory twice.
delete[] MInLocs[bbnum];
delete[] MOutLocs[bbnum];
MInLocs[bbnum] = nullptr;
MOutLocs[bbnum] = nullptr;
SavedLiveIns[bbnum].clear();
}

emitTransfers(AllVarsNumbering);
}

void InstrRefBasedLDV::initialSetup(MachineFunction &MF) {
// Build some useful data structures.

Expand Down Expand Up @@ -2902,8 +2863,175 @@ void InstrRefBasedLDV::initialSetup(MachineFunction &MF) {
#endif
}

// Produce an "ejection map" for blocks, i.e., what's the highest-numbered
// lexical scope it's used in. When exploring in DFS order and we pass that
// scope, the block can be processed and any tracking information freed.
void InstrRefBasedLDV::makeDepthFirstEjectionMap(
SmallVectorImpl<unsigned> &EjectionMap,
const ScopeToDILocT &ScopeToDILocation,
ScopeToAssignBlocksT &ScopeToAssignBlocks) {
SmallPtrSet<const MachineBasicBlock *, 8> BlocksToExplore;
SmallVector<std::pair<LexicalScope *, ssize_t>, 4> WorkStack;
auto *TopScope = LS.getCurrentFunctionScope();

// Unlike lexical scope explorers, we explore in reverse order, to find the
// "last" lexical scope used for each block early.
WorkStack.push_back({TopScope, TopScope->getChildren().size() - 1});

while (!WorkStack.empty()) {
auto &ScopePosition = WorkStack.back();
LexicalScope *WS = ScopePosition.first;
ssize_t ChildNum = ScopePosition.second--;

const SmallVectorImpl<LexicalScope *> &Children = WS->getChildren();
if (ChildNum >= 0) {
// If ChildNum is positive, there are remaining children to explore.
// Push the child and its children-count onto the stack.
auto &ChildScope = Children[ChildNum];
WorkStack.push_back(
std::make_pair(ChildScope, ChildScope->getChildren().size() - 1));
} else {
WorkStack.pop_back();

// We've explored all children and any later blocks: examine all blocks
// in our scope. If they haven't yet had an ejection number set, then
// this scope will be the last to use that block.
auto DILocationIt = ScopeToDILocation.find(WS);
if (DILocationIt != ScopeToDILocation.end()) {
getBlocksForScope(DILocationIt->second, BlocksToExplore,
ScopeToAssignBlocks.find(WS)->second);
for (auto *MBB : BlocksToExplore) {
unsigned BBNum = MBB->getNumber();
if (EjectionMap[BBNum] == 0)
EjectionMap[BBNum] = WS->getDFSOut();
}

BlocksToExplore.clear();
}
}
}
}

bool InstrRefBasedLDV::depthFirstVLocAndEmit(
unsigned MaxNumBlocks, const ScopeToDILocT &ScopeToDILocation,
const ScopeToVarsT &ScopeToVars, ScopeToAssignBlocksT &ScopeToAssignBlocks,
LiveInsT &Output, ValueIDNum **MOutLocs, ValueIDNum **MInLocs,
SmallVectorImpl<VLocTracker> &AllTheVLocs, MachineFunction &MF,
DenseMap<DebugVariable, unsigned> &AllVarsNumbering,
const TargetPassConfig &TPC) {
TTracker = new TransferTracker(TII, MTracker, MF, *TRI, CalleeSavedRegs, TPC);
unsigned NumLocs = MTracker->getNumLocs();
VTracker = nullptr;

// No scopes? No variable locations.
if (!LS.getCurrentFunctionScope())
return false;

// Build map from block number to the last scope that uses the block.
SmallVector<unsigned, 16> EjectionMap;
EjectionMap.resize(MaxNumBlocks, 0);
makeDepthFirstEjectionMap(EjectionMap, ScopeToDILocation,
ScopeToAssignBlocks);

// Helper lambda for ejecting a block -- if nothing is going to use the block,
// we can translate the variable location information into DBG_VALUEs and then
// free all of InstrRefBasedLDV's data structures.
auto EjectBlock = [&](MachineBasicBlock &MBB) -> void {
unsigned BBNum = MBB.getNumber();
AllTheVLocs[BBNum].clear();

// Prime the transfer-tracker, and then step through all the block
// instructions, installing transfers.
MTracker->reset();
MTracker->loadFromArray(MInLocs[BBNum], BBNum);
TTracker->loadInlocs(MBB, MInLocs[BBNum], Output[BBNum], NumLocs);

CurBB = BBNum;
CurInst = 1;
for (auto &MI : MBB) {
process(MI, MOutLocs, MInLocs);
TTracker->checkInstForNewValues(CurInst, MI.getIterator());
++CurInst;
}

// Free machine-location tables for this block.
delete[] MInLocs[BBNum];
delete[] MOutLocs[BBNum];
// Make ourselves brittle to use-after-free errors.
MInLocs[BBNum] = nullptr;
MOutLocs[BBNum] = nullptr;
// We don't need live-in variable values for this block either.
Output[BBNum].clear();
AllTheVLocs[BBNum].clear();
};

SmallPtrSet<const MachineBasicBlock *, 8> BlocksToExplore;
SmallVector<std::pair<LexicalScope *, ssize_t>, 4> WorkStack;
WorkStack.push_back({LS.getCurrentFunctionScope(), 0});
unsigned HighestDFSIn = 0;

// Proceed to explore in depth first order.
while (!WorkStack.empty()) {
auto &ScopePosition = WorkStack.back();
LexicalScope *WS = ScopePosition.first;
ssize_t ChildNum = ScopePosition.second++;

// We obesrve scopes with children twice here, once descending in, once
// ascending out of the scope nest. Use HighestDFSIn as a ratchet to ensure
// we don't process a scope twice. Additionally, ignore scopes that don't
// have a DILocation -- by proxy, this means we never tracked any variable
// assignments in that scope.
auto DILocIt = ScopeToDILocation.find(WS);
if (HighestDFSIn <= WS->getDFSIn() && DILocIt != ScopeToDILocation.end()) {
const DILocation *DILoc = DILocIt->second;
auto &VarsWeCareAbout = ScopeToVars.find(WS)->second;
auto &BlocksInScope = ScopeToAssignBlocks.find(WS)->second;

buildVLocValueMap(DILoc, VarsWeCareAbout, BlocksInScope, Output, MOutLocs,
MInLocs, AllTheVLocs);
}

HighestDFSIn = std::max(HighestDFSIn, WS->getDFSIn());

// Descend into any scope nests.
const SmallVectorImpl<LexicalScope *> &Children = WS->getChildren();
if (ChildNum < (ssize_t)Children.size()) {
// There are children to explore -- push onto stack and continue.
auto &ChildScope = Children[ChildNum];
WorkStack.push_back(std::make_pair(ChildScope, 0));
} else {
WorkStack.pop_back();

// We've explored a leaf, or have explored all the children of a scope.
// Try to eject any blocks where this is the last scope it's relevant to.
auto DILocationIt = ScopeToDILocation.find(WS);
if (DILocationIt == ScopeToDILocation.end())
continue;

getBlocksForScope(DILocationIt->second, BlocksToExplore,
ScopeToAssignBlocks.find(WS)->second);
for (auto *MBB : BlocksToExplore)
if (WS->getDFSOut() == EjectionMap[MBB->getNumber()])
EjectBlock(const_cast<MachineBasicBlock &>(*MBB));

BlocksToExplore.clear();
}
}

// Some artificial blocks may not have been ejected, meaning they're not
// connected to an actual legitimate scope. This can technically happen
// with things like the entry block. In theory, we shouldn't need to do
// anything for such out-of-scope blocks, but for the sake of being similar
// to VarLocBasedLDV, eject these too.
for (auto *MBB : ArtificialBlocks)
if (MOutLocs[MBB->getNumber()])
EjectBlock(*MBB);

return emitTransfers(AllVarsNumbering);
}

bool InstrRefBasedLDV::emitTransfers(
DenseMap<DebugVariable, unsigned> &AllVarsNumbering) {
DenseMap<DebugVariable, unsigned> &AllVarsNumbering) {
// Go through all the transfers recorded in the TransferTracker -- this is
// both the live-ins to a block, and any movements of values that happen
// in the middle.
Expand Down Expand Up @@ -3098,26 +3226,12 @@ bool InstrRefBasedLDV::ExtendRanges(MachineFunction &MF,
delete[] MInLocs[Idx];
}
} else {
// Compute the extended ranges, iterating over scopes. There might be
// something to be said for ordering them by size/locality, but that's for
// the future. For each scope, solve the variable value problem, producing
// a map of variables to values in SavedLiveIns.
for (auto &P : ScopeToVars) {
buildVLocValueMap(ScopeToDILocation[P.first], P.second,
ScopeToAssignBlocks[P.first], SavedLiveIns, MOutLocs, MInLocs,
vlocs);
}

// Now that we've analysed variable assignments, free any tracking data.
vlocs.clear();

// Using the computed value locations and variable values for each block,
// create the DBG_VALUE instructions representing the extended variable
// locations.
emitLocations(MF, SavedLiveIns, MOutLocs, MInLocs, AllVarsNumbering, *TPC);

// Did we actually make any changes? If we created any DBG_VALUEs, then yes.
Changed = TTracker->Transfers.size() != 0;
// Optionally, solve the variable value problem and emit to blocks by using
// a lexical-scope-depth search. It should be functionally identical to
// the "else" block of this condition.
Changed = depthFirstVLocAndEmit(
MaxNumBlocks, ScopeToDILocation, ScopeToVars, ScopeToAssignBlocks,
SavedLiveIns, MOutLocs, MInLocs, vlocs, MF, AllVarsNumbering, *TPC);
}

// Elements of these arrays will be deleted by emitLocations.
Expand Down
34 changes: 22 additions & 12 deletions llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1071,18 +1071,6 @@ class InstrRefBasedLDV : public LDVImpl {
const LiveIdxT &LiveOuts, ValueIDNum **MOutLocs,
const SmallVectorImpl<const MachineBasicBlock *> &BlockOrders);

/// Given the solutions to the two dataflow problems, machine value locations
/// in \p MInLocs and live-in variable values in \p SavedLiveIns, runs the
/// TransferTracker class over the function to produce live-in and transfer
/// DBG_VALUEs, then inserts them. Groups of DBG_VALUEs are inserted in the
/// order given by AllVarsNumbering -- this could be any stable order, but
/// right now "order of appearence in function, when explored in RPO", so
/// that we can compare explictly against VarLocBasedImpl.
void emitLocations(MachineFunction &MF, LiveInsT SavedLiveIns,
ValueIDNum **MOutLocs, ValueIDNum **MInLocs,
DenseMap<DebugVariable, unsigned> &AllVarsNumbering,
const TargetPassConfig &TPC);

/// Take collections of DBG_VALUE instructions stored in TTracker, and
/// install them into their output blocks. Preserves a stable order of
/// DBG_VALUEs produced (which would otherwise cause nondeterminism) through
Expand All @@ -1093,6 +1081,28 @@ class InstrRefBasedLDV : public LDVImpl {
/// RPOT block ordering.
void initialSetup(MachineFunction &MF);

/// Produce a map of the last lexical scope that uses a block, using the
/// scopes DFSOut number. Mapping is block-number to DFSOut.
/// \p EjectionMap Pre-allocated vector in which to install the built ma.
/// \p ScopeToDILocation Mapping of LexicalScopes to their DILocations.
/// \p AssignBlocks Map of blocks where assignments happen for a scope.
void makeDepthFirstEjectionMap(SmallVectorImpl<unsigned> &EjectionMap,
const ScopeToDILocT &ScopeToDILocation,
ScopeToAssignBlocksT &AssignBlocks);

/// When determining per-block variable values and emitting to DBG_VALUEs,
/// this function explores by lexical scope depth. Doing so means that per
/// block information can be fully computed before exploration finishes,
/// allowing us to emit it and free data structures earlier than otherwise.
/// It's also good for locality.
bool depthFirstVLocAndEmit(
unsigned MaxNumBlocks, const ScopeToDILocT &ScopeToDILocation,
const ScopeToVarsT &ScopeToVars, ScopeToAssignBlocksT &ScopeToBlocks,
LiveInsT &Output, ValueIDNum **MOutLocs, ValueIDNum **MInLocs,
SmallVectorImpl<VLocTracker> &AllTheVLocs, MachineFunction &MF,
DenseMap<DebugVariable, unsigned> &AllVarsNumbering,
const TargetPassConfig &TPC);

bool ExtendRanges(MachineFunction &MF, MachineDominatorTree *DomTree,
TargetPassConfig *TPC, unsigned InputBBLimit,
unsigned InputDbgValLimit) override;
Expand Down

0 comments on commit 9fd9d56

Please sign in to comment.