Skip to content

Commit a6027fc

Browse files
committed
[basicblock-utils] Add new API: JointPostDominanceSetComputer and its method findJointPostDominatingSet(...).
Often times when one is working with ownership one has a value V and a set of use points Uses where you want V's lifetime to end at, but those Uses together (while not reachable from each other) only partially post-dominate V. JointPostDominanceSetComputer is a struct that implements a general solution to that operation at the block level. The struct itself is just a set of state that the computation uses so that a pass can clear the state (allowing for us to avoid needing to remalloc if we had any small data structures that went big). To get into the semantics, the API JointPostDominanceSetComputer::findJointPostDominatingSet() takes in a "dominating block" and a "dominated block set" and returns two things to the user: 1. A set of blocks that together with the "dominated block set" jointly-postdominate the "dominating block". 2. A list of blocks in the "dominated block set" that were reachable from any of the other "dominated blocks", including itself in the case of a block in aloop. Conceptually we are performing a backwards walk up the CFG towards the "dominating block" starting at each block in the "dominated block set". As we go, we track successor blocks and report any successor blocks that we do not hit during our traversal as result blocks and are passed to the result callback. Now what does this actually mean: 1. All blocks in the "dominated blockset" that are at the same loop nest level as our dominating block will always be part of the final post-dominating block set. 2. All "lifetime ending" blocks that are at a different loop nest level than our dominating block are not going to be in our final result set. Let LifetimeEndingBlock be such a block. Then note that our assumed condition implies that there must be a sub-loop, SubLoop, at the same level of the loop-nest as the dominating block that contains LifetimeEndingBlock. The algorithm will yield to the caller the exiting blocks of that loop. It will also flag the blocks that were found to be a different use level, so the caller can introduce a copy at that point if needed. NOTE: Part of the reason why I am writing this rather than using the linear lifetime checker (LLChecker) is that LLChecker is being used in too many places because it is convenient. Its original true use was for emitting diagnostics and that can be seen through the implementation. I don't want to add more contortions to that code, so as I am finding new use cases where I could either write something new or add contortions to the LLChecker, I am doing the former.
1 parent 3d91d7f commit a6027fc

File tree

2 files changed

+197
-2
lines changed

2 files changed

+197
-2
lines changed

include/swift/SIL/BasicBlockUtils.h

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
#ifndef SWIFT_SIL_DEADENDBLOCKS_H
14-
#define SWIFT_SIL_DEADENDBLOCKS_H
13+
#ifndef SWIFT_SIL_BASICBLOCKUTILS_H
14+
#define SWIFT_SIL_BASICBLOCKUTILS_H
1515

1616
#include "swift/SIL/SILValue.h"
1717
#include "llvm/ADT/SetVector.h"
18+
#include "llvm/ADT/SmallPtrSet.h"
1819
#include "llvm/ADT/SmallVector.h"
1920

2021
namespace swift {
@@ -90,6 +91,90 @@ class DeadEndBlocks {
9091
}
9192
};
9293

94+
/// A struct that contains the intermediate state used in computing
95+
/// joint-dominance sets. Enables a pass to easily reuse the same small data
96+
/// structures with clearing (noting that clearing our internal state does not
97+
/// cause us to shrink meaning that once we malloc, we keep the malloced
98+
/// memory).
99+
struct JointPostDominanceSetComputer {
100+
/// The worklist that drives the algorithm.
101+
SmallVector<SILBasicBlock *, 32> worklist;
102+
103+
/// A set that guards our worklist. Any block before it is added to worklist
104+
/// should be checked against visitedBlocks.
105+
SmallPtrSet<SILBasicBlock *, 32> visitedBlocks;
106+
107+
/// The set of blocks where we begin our walk.
108+
SmallPtrSet<SILBasicBlock *, 8> initialBlocks;
109+
110+
/// A subset of our initial blocks that we found as a predecessor of another
111+
/// block along our walk.
112+
SmallVector<SILBasicBlock *, 8> reachableInputBlocks;
113+
114+
/// As we process the worklist, any successors that we see that have not been
115+
/// visited yet are placed in here. At the end of our worklist, any blocks
116+
/// that remain here are "leaking blocks" that together with our initial set
117+
/// would provide a jointly-postdominating set of our dominating value.
118+
SmallSetVector<SILBasicBlock *, 8> blocksThatLeakIfNeverVisited;
119+
120+
DeadEndBlocks &deadEndBlocks;
121+
122+
JointPostDominanceSetComputer(DeadEndBlocks &deadEndBlocks)
123+
: deadEndBlocks(deadEndBlocks) {}
124+
125+
void clear() {
126+
worklist.clear();
127+
visitedBlocks.clear();
128+
initialBlocks.clear();
129+
reachableInputBlocks.clear();
130+
blocksThatLeakIfNeverVisited.clear();
131+
}
132+
133+
/// Compute joint-postdominating set for \p dominatingBlock and \p
134+
/// dominatedBlockSet found by walking up the CFG from the latter to the
135+
/// former.
136+
///
137+
/// We pass back the following information via callbacks so our callers can
138+
/// use whatever container they need to:
139+
///
140+
/// * inputBlocksFoundDuringWalk: Any blocks from the "dominated
141+
/// block set" that was found as a predecessor block during our traversal is
142+
/// passed to this callback. These can occur for two reasons:
143+
///
144+
/// 1. We actually had a block in \p dominatedBlockSet that was reachable
145+
/// from another block in said set. This is a valid usage of the API
146+
/// since it could be that the user does not care about such uses and
147+
/// leave this callback empty.
148+
///
149+
/// 2. We had a block in \p dominatedBlockSet that is in a sub-loop in the
150+
/// loop-nest relative to \p dominatingBlock causing us to go around a
151+
/// backedge and hit the block during our traversal. In this case, we
152+
/// have already during the traversal passed the exiting blocks of the
153+
/// sub-loop as joint postdominace completion set blocks. This is useful
154+
/// if one is using this API for lifetime extension purposes of lifetime
155+
/// ending uses and one needs to insert compensating copy_value at these
156+
/// locations due to the lack of strong control-equivalence in between
157+
/// the block and \p dominatingBlock.
158+
///
159+
///
160+
/// * foundJointPostDomSetCompletionBlocks: The set of blocks not in \p
161+
/// dominatedBlockSet that together with \p dominatedBlockSet
162+
/// jointly-postdominate \p dominatedBlock. This is "completing" the joint
163+
/// post-dominance set.
164+
///
165+
/// * inputBlocksInJointPostDomSet: Any of our input blocks that were never
166+
/// found as a predecessor is passed to this callback. This block is in the
167+
/// final minimal joint-postdominance set and is passed to this
168+
/// callback. This is optional and we will avoid doing work if it is not
169+
/// set.
170+
void findJointPostDominatingSet(
171+
SILBasicBlock *dominatingBlock,
172+
ArrayRef<SILBasicBlock *> dominatedBlockSet,
173+
function_ref<void(SILBasicBlock *)> inputBlocksFoundDuringWalk,
174+
function_ref<void(SILBasicBlock *)> foundJointPostDomSetCompletionBlocks,
175+
function_ref<void(SILBasicBlock *)> inputBlocksInJointPostDomSet = {});
176+
};
177+
93178
} // namespace swift
94179

95180
#endif

lib/SIL/Utils/BasicBlockUtils.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "swift/SIL/BasicBlockUtils.h"
14+
#include "swift/Basic/STLExtras.h"
1415
#include "swift/SIL/Dominance.h"
1516
#include "swift/SIL/LoopInfo.h"
1617
#include "swift/SIL/SILArgument.h"
1718
#include "swift/SIL/SILBasicBlock.h"
1819
#include "swift/SIL/SILBuilder.h"
1920
#include "swift/SIL/SILFunction.h"
2021
#include "swift/SIL/TerminatorUtils.h"
22+
#include "llvm/ADT/STLExtras.h"
2123

2224
using namespace swift;
2325

@@ -378,3 +380,111 @@ void DeadEndBlocks::compute() {
378380
ReachableBlocks.insert(Pred);
379381
}
380382
}
383+
384+
//===----------------------------------------------------------------------===//
385+
// Post Dominance Set Completion Utilities
386+
//===----------------------------------------------------------------------===//
387+
388+
void JointPostDominanceSetComputer::findJointPostDominatingSet(
389+
SILBasicBlock *dominatingBlock, ArrayRef<SILBasicBlock *> dominatedBlockSet,
390+
function_ref<void(SILBasicBlock *)> foundInputBlocksNotInJointPostDomSet,
391+
function_ref<void(SILBasicBlock *)> foundJointPostDomSetCompletionBlocks,
392+
function_ref<void(SILBasicBlock *)> foundInputBlocksInJointPostDomSet) {
393+
// If our reachable block set is empty, assert. This is most likely programmer
394+
// error.
395+
assert(dominatedBlockSet.size() != 0);
396+
397+
// If we have a reachable block set with a single block and that block is
398+
// dominatingBlock, then we return success since a block post-doms its self so
399+
// it is already complete.
400+
if (dominatedBlockSet.size() == 1) {
401+
if (dominatingBlock == dominatedBlockSet[0]) {
402+
if (foundInputBlocksInJointPostDomSet)
403+
foundInputBlocksInJointPostDomSet(dominatingBlock);
404+
return;
405+
}
406+
}
407+
408+
// At the top of where we for sure are going to use state... make sure we
409+
// always clean up any resources that we use!
410+
SWIFT_DEFER { clear(); };
411+
412+
// Otherwise, we need to compute our joint post dominating set. We do this by
413+
// performing a backwards walk up the CFG tracking back liveness until we find
414+
// our dominating block. As we walk up, we keep track of any successor blocks
415+
// that we need to visit before the walk completes lest we leak. After we
416+
// finish the walk, these leaking blocks are a valid (albeit not unique)
417+
// completion of the post dom set.
418+
for (auto *block : dominatedBlockSet) {
419+
// Skip dead end blocks.
420+
if (deadEndBlocks.isDeadEnd(block))
421+
continue;
422+
423+
// We require dominatedBlockSet to be a set and thus assert if we hit it to
424+
// flag user error to our caller.
425+
bool succeededInserting = visitedBlocks.insert(block).second;
426+
(void)succeededInserting;
427+
assert(succeededInserting &&
428+
"Repeat Elt: dominatedBlockSet should be a set?!");
429+
initialBlocks.insert(block);
430+
worklist.push_back(block);
431+
}
432+
433+
// Then until we run out of blocks...
434+
while (!worklist.empty()) {
435+
auto *block = worklist.pop_back_val();
436+
437+
// First remove block from blocksThatLeakIfNeverVisited if it is there since
438+
// we know that it isn't leaking since we are visiting it now.
439+
blocksThatLeakIfNeverVisited.remove(block);
440+
441+
// Then if our block is not one of our initial blocks, add the block's
442+
// successors to blocksThatLeakIfNeverVisited.
443+
if (!initialBlocks.count(block)) {
444+
for (auto *succBlock : block->getSuccessorBlocks()) {
445+
if (visitedBlocks.count(succBlock))
446+
continue;
447+
if (deadEndBlocks.isDeadEnd(succBlock))
448+
continue;
449+
blocksThatLeakIfNeverVisited.insert(succBlock);
450+
}
451+
}
452+
453+
// If we are the dominating block, we are done.
454+
if (dominatingBlock == block)
455+
continue;
456+
457+
// Otherwise for each predecessor that we have, first check if it was one of
458+
// our initial blocks (signaling a loop) and then add it to the worklist if
459+
// we haven't visited it already.
460+
for (auto *predBlock : block->getPredecessorBlocks()) {
461+
if (initialBlocks.count(predBlock)) {
462+
reachableInputBlocks.push_back(predBlock);
463+
}
464+
if (visitedBlocks.insert(predBlock).second)
465+
worklist.push_back(predBlock);
466+
}
467+
}
468+
469+
// After our worklist has emptied, any blocks left in
470+
// blocksThatLeakIfNeverVisited are "leaking blocks".
471+
for (auto *leakingBlock : blocksThatLeakIfNeverVisited)
472+
foundJointPostDomSetCompletionBlocks(leakingBlock);
473+
474+
// Then unique our list of reachable input blocks and pass them to our
475+
// callback.
476+
sortUnique(reachableInputBlocks);
477+
for (auto *block : reachableInputBlocks)
478+
foundInputBlocksNotInJointPostDomSet(block);
479+
480+
// Then if were asked to find the subset of our input blocks that are in the
481+
// joint-postdominance set, compute that.
482+
if (!foundInputBlocksInJointPostDomSet)
483+
return;
484+
485+
// Pass back the reachable input blocks that were not reachable from other
486+
// input blocks to.
487+
for (auto *block : dominatedBlockSet)
488+
if (lower_bound(reachableInputBlocks, block) == reachableInputBlocks.end())
489+
foundInputBlocksInJointPostDomSet(block);
490+
}

0 commit comments

Comments
 (0)