Skip to content

Commit 0f20144

Browse files
authored
Merge pull request #66846 from kavon/standardize-workqueue
Add a `BasicBlockWorkqueue` as a common utility.
2 parents 15f7a28 + 057b1e5 commit 0f20144

File tree

4 files changed

+112
-59
lines changed

4 files changed

+112
-59
lines changed

include/swift/SIL/BasicBlockDatastructures.h

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "swift/SIL/StackList.h"
2121
#include "swift/SIL/BasicBlockBits.h"
22+
#include <deque>
2223

2324
namespace swift {
2425

@@ -61,10 +62,12 @@ class BasicBlockSetVector {
6162
}
6263
};
6364

64-
/// A utility for processing basic blocks in a worklist.
65+
/// A utility for processing basic blocks in a worklist that uses
66+
/// last-in-first-out order.
6567
///
66-
/// It is basically a combination of a block vector and a block set. It can be
67-
/// used for typical worklist-processing algorithms.
68+
/// It is basically a combination of a stack list and a block set. It can be
69+
/// used for typical worklist-processing algorithms and is recommended in
70+
/// nearly all situations as its implementation is well-optimized.
6871
class BasicBlockWorklist {
6972
StackList<SILBasicBlock *> worklist;
7073
BasicBlockSet visited;
@@ -119,6 +122,70 @@ class BasicBlockWorklist {
119122
bool isVisited(SILBasicBlock *block) const { return visited.contains(block); }
120123
};
121124

125+
126+
/// A utility for processing basic blocks in a work-queue that uses
127+
/// first-in-first-out order.
128+
///
129+
/// It is basically a combination of a std::deque and a block set. It can be
130+
/// used for certain kinds of worklist-processing algorithms.
131+
///
132+
/// When in doubt, you should reach for a BasicBlockWorklist instead.
133+
class BasicBlockWorkqueue {
134+
std::deque<SILBasicBlock*> workqueue;
135+
BasicBlockSet visited;
136+
137+
public:
138+
/// Construct an empty workqueue.
139+
BasicBlockWorkqueue(SILFunction *function)
140+
: workqueue(), visited(function) {}
141+
142+
/// Initialize the workqueue with \p initialBlock.
143+
BasicBlockWorkqueue(SILBasicBlock *initialBlock)
144+
: BasicBlockWorkqueue(initialBlock->getParent()) {
145+
push(initialBlock);
146+
}
147+
148+
/// Pops the next element from the front of the workqueue or returns null,
149+
/// if the workqueue is empty.
150+
SILBasicBlock *pop() {
151+
if (workqueue.empty())
152+
return nullptr;
153+
SILBasicBlock *block = workqueue.front();
154+
workqueue.pop_front();
155+
return block;
156+
}
157+
158+
/// Pushes \p block onto the end of the workqueue if \p block has never been
159+
/// pushed before.
160+
bool pushIfNotVisited(SILBasicBlock *block) {
161+
if (visited.insert(block)) {
162+
workqueue.push_back(block);
163+
return true;
164+
}
165+
return false;
166+
}
167+
168+
/// Like `pushIfNotVisited`, but requires that \p block has never been on the
169+
/// workqueue before.
170+
void push(SILBasicBlock *block) {
171+
assert(!visited.contains(block));
172+
visited.insert(block);
173+
workqueue.push_back(block);
174+
}
175+
176+
/// Like `pop`, but marks the returned block as "unvisited". This means, that
177+
/// the block can be pushed onto the queue again.
178+
SILBasicBlock *popAndForget() {
179+
SILBasicBlock *block = pop();
180+
if (block)
181+
visited.erase(block);
182+
return block;
183+
}
184+
185+
/// Returns true if \p block was visited, i.e. has been added to the workqueue
186+
bool isVisited(SILBasicBlock *block) const { return visited.contains(block); }
187+
};
188+
122189
} // namespace swift
123190

124191
#endif

lib/SILOptimizer/Differentiation/PullbackCloner.cpp

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,25 +1890,16 @@ bool PullbackCloner::Implementation::run() {
18901890
// ignored.
18911891
// The original blocks in traversal order for pullback generation.
18921892
SmallVector<SILBasicBlock *, 8> originalBlocks;
1893-
// The set of visited original blocks.
1894-
SmallDenseSet<SILBasicBlock *, 8> visitedBlocks;
1893+
// The workqueue used for bookkeeping during the breadth-first traversal.
1894+
BasicBlockWorkqueue workqueue = {originalExitBlock};
18951895

18961896
// Perform BFS from the original exit block.
18971897
{
1898-
std::deque<SILBasicBlock *> worklist = {};
1899-
worklist.push_back(originalExitBlock);
1900-
visitedBlocks.insert(originalExitBlock);
1901-
while (!worklist.empty()) {
1902-
auto *BB = worklist.front();
1903-
worklist.pop_front();
1904-
1898+
while (auto *BB = workqueue.pop()) {
19051899
originalBlocks.push_back(BB);
19061900

19071901
for (auto *nextBB : BB->getPredecessorBlocks()) {
1908-
if (!visitedBlocks.count(nextBB)) {
1909-
worklist.push_back(nextBB);
1910-
visitedBlocks.insert(nextBB);
1911-
}
1902+
workqueue.pushIfNotVisited(nextBB);
19121903
}
19131904
}
19141905
}
@@ -2004,7 +1995,7 @@ bool PullbackCloner::Implementation::run() {
20041995
// pullback original block, passing adjoint values of active values.
20051996
for (auto *succBB : origBB->getSuccessorBlocks()) {
20061997
// Skip generating pullback block for original unreachable blocks.
2007-
if (!visitedBlocks.count(succBB))
1998+
if (!workqueue.isVisited(succBB))
20081999
continue;
20092000
auto *pullbackTrampolineBB = pullback.createBasicBlockBefore(pullbackBB);
20102001
pullbackTrampolineBBMap.insert({{origBB, succBB}, pullbackTrampolineBB});

lib/SILOptimizer/Mandatory/FlowIsolation.cpp

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "swift/SIL/BitDataflow.h"
2121
#include "swift/SIL/BasicBlockBits.h"
2222
#include "swift/SIL/DebugUtils.h"
23+
#include "swift/SIL/BasicBlockDatastructures.h"
2324
#include "swift/SILOptimizer/PassManager/Transforms.h"
2425

2526
using namespace swift;
@@ -277,34 +278,13 @@ SILInstruction *AnalysisInfo::findNonisolatedBlame(SILInstruction* startInst) {
277278
SILBasicBlock* firstBlk = startInst->getParent();
278279
assert(firstBlk->getParent() == getFunction());
279280

280-
// workList for breadth-first search to find one of the closest blocks.
281-
std::deque<SILBasicBlock*> workList;
282-
BasicBlockSet visited(getFunction());
283-
284-
// seed the search
285-
workList.push_back(firstBlk);
286-
SILBasicBlock::reverse_iterator cursor = startInst->getReverseIterator();
287-
288-
while (!workList.empty()) {
289-
auto *block = workList.front();
290-
workList.pop_front();
281+
// searches the a block starting at the provided position in reverse
282+
// order of instructions (i.e., from terminator to first instruction).
283+
auto searchBlockForNonisolated =
284+
[&](SILBasicBlock::reverse_iterator cursor) -> SILInstruction * {
285+
SILBasicBlock *block = cursor->getParent();
291286
auto &state = flow[block];
292287

293-
// if this block doesn't have exiting nonisolation, then there's no
294-
// way we'll find nonisolation in a predecessor.
295-
assert(state.exitSet[State::Nonisolated] && "nonisolation is unreachable!");
296-
297-
// If this is the first time we're scanning the start block, then leave
298-
// the cursor alone and do a partial scan. If the block is part of
299-
// a cycle, we want to scan the block entirely on the second visit, so we
300-
// do not count this as a visit.
301-
if (startInst) {
302-
startInst = nullptr; // make sure second visit scans entirely.
303-
} else {
304-
cursor = block->rbegin();
305-
visited.insert(block);
306-
}
307-
308288
// does this block generate non-isolation?
309289
if (state.genSet[State::Nonisolated]) {
310290
auto &data = this->operator[](block);
@@ -321,16 +301,36 @@ SILInstruction *AnalysisInfo::findNonisolatedBlame(SILInstruction* startInst) {
321301
}
322302
}
323303

324-
for (auto *pred : block->getPredecessorBlocks()) {
325-
// skip visited
326-
if (visited.contains(pred))
327-
continue;
304+
return nullptr;
305+
};
306+
307+
// whether we should visit a given predecessor block in the search.
308+
auto shouldVisit = [&](SILBasicBlock *pred) {
309+
// visit blocks that contribute nonisolation to successors.
310+
return flow[pred].exitSet[State::Nonisolated];
311+
};
328312

329-
// skip blocks that do not contribute nonisolation.
330-
if (flow[pred].exitSet[State::Nonisolated] == false)
331-
continue;
313+
// first check if the nonisolated use precedes the start instruction in
314+
// this same block.
315+
if (auto *inst = searchBlockForNonisolated(startInst->getReverseIterator()))
316+
return inst;
332317

333-
workList.push_back(pred);
318+
// Seed a workQueue with the predecessors of this start block to
319+
// begin a breadth-first search to find one of the closest predecessors.
320+
BasicBlockWorkqueue workQueue(firstBlk->getFunction());
321+
for (auto *pred : firstBlk->getPredecessorBlocks())
322+
if (shouldVisit(pred))
323+
workQueue.push(pred);
324+
325+
while (auto *block = workQueue.pop()) {
326+
// do we have a nonisolated use here?
327+
if (auto *inst = searchBlockForNonisolated(block->rbegin()))
328+
return inst;
329+
330+
// otherwise keep looking
331+
for (auto *pred : block->getPredecessorBlocks()) {
332+
if (shouldVisit(pred))
333+
workQueue.pushIfNotVisited(pred);
334334
}
335335
}
336336

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,8 @@ void DiagnosticEmitter::emitMissingConsumeInDiscardingContext(
290290
// / \ / \
291291
// d d . .
292292
//
293-
BasicBlockSet visited(destroyPoint->getFunction());
294-
std::deque<SILBasicBlock *> bfsWorklist = {destroyPoint->getParent()};
295-
while (auto *bb = bfsWorklist.front()) {
296-
visited.insert(bb);
297-
bfsWorklist.pop_front();
298-
293+
BasicBlockWorkqueue bfsWorklist = {destroyPoint->getParent()};
294+
while (auto *bb = bfsWorklist.pop()) {
299295
TermInst *term = bb->getTerminator();
300296

301297
// Looking for a block that exits the function or terminates the program.
@@ -315,8 +311,7 @@ void DiagnosticEmitter::emitMissingConsumeInDiscardingContext(
315311
}
316312

317313
for (auto *nextBB : term->getSuccessorBlocks())
318-
if (!visited.contains(nextBB))
319-
bfsWorklist.push_back(nextBB);
314+
bfsWorklist.pushIfNotVisited(nextBB);
320315
}
321316
}
322317

0 commit comments

Comments
 (0)