Skip to content

Commit beb2ae7

Browse files
[MemProf] Refactor and clean up edge removal (#109188)
Add helper for removing an edge from the graph, and for checking if an edge has been removed from the graph, and then update code to use those consistently for removal and during edge iteration, respectively. Also fix a couple of places that were incorrectly iterating over edge lists that could in theory be updated during the iteration.
1 parent ddd5741 commit beb2ae7

File tree

1 file changed

+104
-33
lines changed

1 file changed

+104
-33
lines changed

llvm/lib/Transforms/IPO/MemProfContextDisambiguation.cpp

Lines changed: 104 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,29 @@ class CallsiteContextGraph {
417417

418418
DenseSet<uint32_t> &getContextIds() { return ContextIds; }
419419

420+
// Helper to clear the fields of this edge when we are removing it from the
421+
// graph.
422+
inline void clear() {
423+
ContextIds.clear();
424+
AllocTypes = (uint8_t)AllocationType::None;
425+
Caller = nullptr;
426+
Callee = nullptr;
427+
}
428+
429+
// Check if edge was removed from the graph. This is useful while iterating
430+
// over a copy of edge lists when performing operations that mutate the
431+
// graph in ways that might remove one of the edges.
432+
inline bool isRemoved() const {
433+
if (Callee || Caller)
434+
return false;
435+
// Any edges that have been removed from the graph but are still in a
436+
// shared_ptr somewhere should have all fields null'ed out by clear()
437+
// above.
438+
assert(AllocTypes == (uint8_t)AllocationType::None);
439+
assert(ContextIds.empty());
440+
return true;
441+
}
442+
420443
void dump() const;
421444
void print(raw_ostream &OS) const;
422445

@@ -485,6 +508,15 @@ class CallsiteContextGraph {
485508
DenseSet<uint32_t> ContextIds;
486509
};
487510

511+
/// Helper to remove edge from graph, updating edge iterator if it is provided
512+
/// (in which case CalleeIter indicates which edge list is being iterated).
513+
/// This will also perform the necessary clearing of the ContextEdge members
514+
/// to enable later checking if the edge has been removed (since we may have
515+
/// other copies of the shared_ptr in existence, and in fact rely on this to
516+
/// enable removal while iterating over a copy of a node's edge list).
517+
void removeEdgeFromGraph(ContextEdge *Edge, EdgeIter *EI = nullptr,
518+
bool CalleeIter = true);
519+
488520
/// Assigns the given Node to calls at or inlined into the location with
489521
/// the Node's stack id, after post order traversing and processing its
490522
/// caller nodes. Uses the call information recorded in the given
@@ -920,15 +952,42 @@ void CallsiteContextGraph<DerivedCCG, FuncTy, CallTy>::ContextNode::
920952
Caller->CalleeEdges.push_back(Edge);
921953
}
922954

955+
template <typename DerivedCCG, typename FuncTy, typename CallTy>
956+
void CallsiteContextGraph<DerivedCCG, FuncTy, CallTy>::removeEdgeFromGraph(
957+
ContextEdge *Edge, EdgeIter *EI, bool CalleeIter) {
958+
assert(!EI || (*EI)->get() == Edge);
959+
// Save the Caller and Callee pointers so we can erase Edge from their edge
960+
// lists after clearing Edge below. We do the clearing first in case it is
961+
// destructed after removing from the edge lists (if those were the last
962+
// shared_ptr references to Edge).
963+
auto *Callee = Edge->Callee;
964+
auto *Caller = Edge->Caller;
965+
966+
// Make sure the edge fields are cleared out so we can properly detect
967+
// removed edges if Edge is not destructed because there is still a shared_ptr
968+
// reference.
969+
Edge->clear();
970+
971+
if (!EI) {
972+
Callee->eraseCallerEdge(Edge);
973+
Caller->eraseCalleeEdge(Edge);
974+
} else if (CalleeIter) {
975+
Callee->eraseCallerEdge(Edge);
976+
*EI = Caller->CalleeEdges.erase(*EI);
977+
} else {
978+
Caller->eraseCalleeEdge(Edge);
979+
*EI = Callee->CallerEdges.erase(*EI);
980+
}
981+
}
982+
923983
template <typename DerivedCCG, typename FuncTy, typename CallTy>
924984
void CallsiteContextGraph<
925985
DerivedCCG, FuncTy, CallTy>::removeNoneTypeCalleeEdges(ContextNode *Node) {
926986
for (auto EI = Node->CalleeEdges.begin(); EI != Node->CalleeEdges.end();) {
927987
auto Edge = *EI;
928988
if (Edge->AllocTypes == (uint8_t)AllocationType::None) {
929989
assert(Edge->ContextIds.empty());
930-
Edge->Callee->eraseCallerEdge(Edge.get());
931-
EI = Node->CalleeEdges.erase(EI);
990+
removeEdgeFromGraph(Edge.get(), &EI, /*CalleeIter=*/true);
932991
} else
933992
++EI;
934993
}
@@ -1197,13 +1256,7 @@ void CallsiteContextGraph<DerivedCCG, FuncTy, CallTy>::connectNewNode(
11971256
}
11981257
// Remove old edge if context ids empty.
11991258
if (Edge->getContextIds().empty()) {
1200-
if (TowardsCallee) {
1201-
Edge->Callee->eraseCallerEdge(Edge.get());
1202-
EI = OrigNode->CalleeEdges.erase(EI);
1203-
} else {
1204-
Edge->Caller->eraseCalleeEdge(Edge.get());
1205-
EI = OrigNode->CallerEdges.erase(EI);
1206-
}
1259+
removeEdgeFromGraph(Edge.get(), &EI, TowardsCallee);
12071260
continue;
12081261
}
12091262
++EI;
@@ -1272,8 +1325,10 @@ void CallsiteContextGraph<DerivedCCG, FuncTy, CallTy>::
12721325
auto CallerEdges = Node->CallerEdges;
12731326
for (auto &Edge : CallerEdges) {
12741327
// Skip any that have been removed during the recursion.
1275-
if (!Edge)
1328+
if (Edge->isRemoved()) {
1329+
assert(!is_contained(Node->CallerEdges, Edge));
12761330
continue;
1331+
}
12771332
assignStackNodesPostOrder(Edge->Caller, Visited, StackIdToMatchingCalls,
12781333
CallToMatchingCall);
12791334
}
@@ -1402,10 +1457,8 @@ void CallsiteContextGraph<DerivedCCG, FuncTy, CallTy>::
14021457
auto *PrevEdge = CurNode->findEdgeFromCallee(PrevNode);
14031458
assert(PrevEdge);
14041459
set_subtract(PrevEdge->getContextIds(), SavedContextIds);
1405-
if (PrevEdge->getContextIds().empty()) {
1406-
PrevNode->eraseCallerEdge(PrevEdge);
1407-
CurNode->eraseCalleeEdge(PrevEdge);
1408-
}
1460+
if (PrevEdge->getContextIds().empty())
1461+
removeEdgeFromGraph(PrevEdge);
14091462
}
14101463
// Since we update the edges from leaf to tail, only look at the callee
14111464
// edges. This isn't an alloc node, so if there are no callee edges, the
@@ -2076,15 +2129,19 @@ bool CallsiteContextGraph<DerivedCCG, FuncTy, CallTy>::calleesMatch(
20762129
// Hook up edge's original caller to new callee node.
20772130
AddEdge(Edge->Caller, CurCalleeNode);
20782131

2132+
#ifndef NDEBUG
2133+
// Save this because Edge's fields get cleared below when removed.
2134+
auto *Caller = Edge->Caller;
2135+
#endif
2136+
20792137
// Remove old edge
2080-
Edge->Callee->eraseCallerEdge(Edge.get());
2081-
EI = Edge->Caller->CalleeEdges.erase(EI);
2138+
removeEdgeFromGraph(Edge.get(), &EI, /*CalleeIter=*/true);
20822139

20832140
// To simplify the increment of EI in the caller, subtract one from EI.
20842141
// In the final AddEdge call we would have either added a new callee edge,
20852142
// to Edge->Caller, or found an existing one. Either way we are guaranteed
20862143
// that there is at least one callee edge.
2087-
assert(!Edge->Caller->CalleeEdges.empty());
2144+
assert(!Caller->CalleeEdges.empty());
20882145
--EI;
20892146

20902147
return true;
@@ -2667,30 +2724,30 @@ void CallsiteContextGraph<DerivedCCG, FuncTy, CallTy>::
26672724
// If we are moving all of Edge's ids, then just move the whole Edge.
26682725
// Otherwise only move the specified subset, to a new edge if needed.
26692726
if (Edge->getContextIds().size() == ContextIdsToMove.size()) {
2727+
// First, update the alloc types on New Callee from Edge.
2728+
// Do this before we potentially clear Edge's fields below!
2729+
NewCallee->AllocTypes |= Edge->AllocTypes;
26702730
// Moving the whole Edge.
2671-
if (CallerEdgeI)
2672-
*CallerEdgeI = OldCallee->CallerEdges.erase(*CallerEdgeI);
2673-
else
2674-
OldCallee->eraseCallerEdge(Edge.get());
26752731
if (ExistingEdgeToNewCallee) {
26762732
// Since we already have an edge to NewCallee, simply move the ids
26772733
// onto it, and remove the existing Edge.
26782734
ExistingEdgeToNewCallee->getContextIds().insert(ContextIdsToMove.begin(),
26792735
ContextIdsToMove.end());
26802736
ExistingEdgeToNewCallee->AllocTypes |= Edge->AllocTypes;
26812737
assert(Edge->ContextIds == ContextIdsToMove);
2682-
Edge->ContextIds.clear();
2683-
Edge->AllocTypes = (uint8_t)AllocationType::None;
2684-
Edge->Caller->eraseCalleeEdge(Edge.get());
2738+
removeEdgeFromGraph(Edge.get(), CallerEdgeI, /*CalleeIter=*/false);
26852739
} else {
26862740
// Otherwise just reconnect Edge to NewCallee.
26872741
Edge->Callee = NewCallee;
26882742
NewCallee->CallerEdges.push_back(Edge);
2743+
// Remove it from callee where it was previously connected.
2744+
if (CallerEdgeI)
2745+
*CallerEdgeI = OldCallee->CallerEdges.erase(*CallerEdgeI);
2746+
else
2747+
OldCallee->eraseCallerEdge(Edge.get());
26892748
// Don't need to update Edge's context ids since we are simply
26902749
// reconnecting it.
26912750
}
2692-
// In either case, need to update the alloc types on New Callee.
2693-
NewCallee->AllocTypes |= Edge->AllocTypes;
26942751
} else {
26952752
// Only moving a subset of Edge's ids.
26962753
if (CallerEdgeI)
@@ -2783,7 +2840,7 @@ void CallsiteContextGraph<DerivedCCG, FuncTy, CallTy>::
27832840
auto CallerEdges = Node->CallerEdges;
27842841
for (auto &Edge : CallerEdges) {
27852842
// Skip any that have been removed by an earlier recursive call.
2786-
if (Edge->Callee == nullptr && Edge->Caller == nullptr) {
2843+
if (Edge->isRemoved()) {
27872844
assert(!is_contained(Node->CallerEdges, Edge));
27882845
continue;
27892846
}
@@ -2844,8 +2901,8 @@ void CallsiteContextGraph<DerivedCCG, FuncTy, CallTy>::identifyClones(
28442901
auto CallerEdges = Node->CallerEdges;
28452902
for (auto &Edge : CallerEdges) {
28462903
// Skip any that have been removed by an earlier recursive call.
2847-
if (Edge->Callee == nullptr && Edge->Caller == nullptr) {
2848-
assert(!llvm::count(Node->CallerEdges, Edge));
2904+
if (Edge->isRemoved()) {
2905+
assert(!is_contained(Node->CallerEdges, Edge));
28492906
continue;
28502907
}
28512908
// Ignore any caller we previously visited via another edge.
@@ -3289,10 +3346,17 @@ bool CallsiteContextGraph<DerivedCCG, FuncTy, CallTy>::assignFunctions() {
32893346
// Reset the CallsiteToCalleeFuncCloneMap entry for any callers
32903347
// that were previously assigned to call PreviousAssignedFuncClone,
32913348
// to record that they now call NewFuncClone.
3292-
for (auto CE : Clone->CallerEdges) {
3349+
// The none type edge removal may remove some of this Clone's caller
3350+
// edges, if it is reached via another of its caller's callees.
3351+
// Iterate over a copy and skip any that were removed.
3352+
auto CallerEdges = Clone->CallerEdges;
3353+
for (auto CE : CallerEdges) {
32933354
// Skip any that have been removed on an earlier iteration.
3294-
if (!CE)
3355+
if (CE->isRemoved()) {
3356+
assert(!is_contained(Clone->CallerEdges, CE));
32953357
continue;
3358+
}
3359+
assert(CE);
32963360
// Ignore any caller that does not have a recorded callsite Call.
32973361
if (!CE->Caller->hasCall())
32983362
continue;
@@ -3315,11 +3379,18 @@ bool CallsiteContextGraph<DerivedCCG, FuncTy, CallTy>::assignFunctions() {
33153379
// accordingly. This is important since we subsequently update the
33163380
// calls from the nodes in the graph and their assignments to callee
33173381
// functions recorded in CallsiteToCalleeFuncCloneMap.
3318-
for (auto CalleeEdge : CE->Caller->CalleeEdges) {
3382+
// The none type edge removal may remove some of this caller's
3383+
// callee edges, if it is reached via another of its callees.
3384+
// Iterate over a copy and skip any that were removed.
3385+
auto CalleeEdges = CE->Caller->CalleeEdges;
3386+
for (auto CalleeEdge : CalleeEdges) {
33193387
// Skip any that have been removed on an earlier iteration when
33203388
// cleaning up newly None type callee edges.
3321-
if (!CalleeEdge)
3389+
if (CalleeEdge->isRemoved()) {
3390+
assert(!is_contained(CE->Caller->CalleeEdges, CalleeEdge));
33223391
continue;
3392+
}
3393+
assert(CalleeEdge);
33233394
ContextNode *Callee = CalleeEdge->Callee;
33243395
// Skip the current callsite, we are looking for other
33253396
// callsites Caller calls, as well as any that does not have a

0 commit comments

Comments
 (0)