Skip to content

Commit c056ca6

Browse files
committed
Add PrunedLivenessBoundary mini-abstraction
Simple wrapper to compute the boundary of PrunedLiveness. Pervasively useful utility for working with OSSA reference lifetimes and borrow scopes. This can also replace the implementation in CanonicalOSSALifetime. This will greatly simplify that utility's logic, preparing it to handle diagnostics (like move-only SILValue checking) and other features.
1 parent ec64297 commit c056ca6

File tree

2 files changed

+207
-8
lines changed

2 files changed

+207
-8
lines changed

include/swift/SIL/PrunedLiveness.h

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@
9494

9595
namespace swift {
9696

97+
class DeadEndBlocks;
98+
9799
/// Discover "pruned" liveness for an arbitrary set of uses. The client builds
98100
/// liveness by first initializing "def" blocks, then incrementally feeding uses
99101
/// to updateForUse().
@@ -133,10 +135,18 @@ class PrunedLiveBlocks {
133135
// the value is also liveout of the block.
134136
llvm::SmallDenseMap<SILBasicBlock *, bool, 4> liveBlocks;
135137

138+
// Optional vector of live blocks for clients that deterministically iterate.
139+
SmallVectorImpl<SILBasicBlock *> *discoveredBlocks;
140+
136141
// Once the first use has been seen, no definitions can be added.
137142
SWIFT_ASSERT_ONLY_DECL(bool seenUse = false);
138143

139144
public:
145+
PrunedLiveBlocks(SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr)
146+
: discoveredBlocks(discoveredBlocks) {
147+
assert(!discoveredBlocks || discoveredBlocks->empty());
148+
}
149+
140150
bool empty() const { return liveBlocks.empty(); }
141151

142152
void clear() {
@@ -146,6 +156,12 @@ class PrunedLiveBlocks {
146156

147157
unsigned numLiveBlocks() const { return liveBlocks.size(); }
148158

159+
/// If the constructor was provided with a vector to populate, then this
160+
/// returns the list of all live blocks with no duplicates.
161+
ArrayRef<SILBasicBlock *> getDiscoveredBlocks() const {
162+
return *discoveredBlocks;
163+
}
164+
149165
void initializeDefBlock(SILBasicBlock *defBB) {
150166
assert(!seenUse && "cannot initialize more defs with partial liveness");
151167
markBlockLive(defBB, LiveWithin);
@@ -165,6 +181,8 @@ class PrunedLiveBlocks {
165181
void markBlockLive(SILBasicBlock *bb, IsLive isLive) {
166182
assert(isLive != Dead && "erasing live blocks isn't implemented.");
167183
liveBlocks[bb] = (isLive == LiveOut);
184+
if (discoveredBlocks)
185+
discoveredBlocks->push_back(bb);
168186
}
169187

170188
void computeUseBlockLiveness(SILBasicBlock *userBB);
@@ -173,13 +191,18 @@ class PrunedLiveBlocks {
173191
/// PrunedLiveness tracks PrunedLiveBlocks along with "interesting" use
174192
/// points. The set of interesting uses is a superset of all uses on the
175193
/// liveness boundary. Filtering out uses that are obviously not on the liveness
176-
/// boundary improves efficiency over tracking all uses. Additionally, all
177-
/// interesting uses that are "lifetime-ending" are flagged. These uses must be
178-
/// on the liveness boundary by their nature, regardless of any other uses. It
179-
/// is up to the client to determine which uses are lifetime-ending. In OSSA,
180-
/// the lifetime-ending property might be detemined by
194+
/// boundary improves efficiency over tracking all uses.
195+
///
196+
/// Additionally, all interesting uses that are potentially "lifetime-ending"
197+
/// are flagged. These instruction are included as interesting use points, even
198+
/// if they don't occur on the liveness boundary. Lifetime-ending uses that end
199+
/// up on the final liveness boundary may be used to end the lifetime. It is up
200+
/// to the client to determine which uses are potentially lifetime-ending. In
201+
/// OSSA, the lifetime-ending property might be detemined by
181202
/// OwnershipConstraint::isLifetimeEnding(). In non-OSSA, it might be determined
182-
/// by deallocation.
203+
/// by deallocation. If a lifetime-ending use ends up within the liveness
204+
/// boundary, then it is up to the client to figure out how to "extend" the
205+
/// lifetime beyond those uses.
183206
///
184207
/// Note: unlike OwnershipLiveRange, this represents a lifetime in terms of the
185208
/// CFG boundary rather that the use set, and, because it is "pruned", it only
@@ -201,6 +224,9 @@ class PrunedLiveness {
201224
llvm::SmallDenseMap<SILInstruction *, bool, 8> users;
202225

203226
public:
227+
PrunedLiveness(SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr)
228+
: liveBlocks(discoveredBlocks) {}
229+
204230
bool empty() const {
205231
assert(!liveBlocks.empty() || users.empty());
206232
return liveBlocks.empty();
@@ -213,6 +239,12 @@ class PrunedLiveness {
213239

214240
unsigned numLiveBlocks() const { return liveBlocks.numLiveBlocks(); }
215241

242+
/// If the constructor was provided with a vector to populate, then this
243+
/// returns the list of all live blocks with no duplicates.
244+
ArrayRef<SILBasicBlock *> getDiscoveredBlocks() const {
245+
return liveBlocks.getDiscoveredBlocks();
246+
}
247+
216248
void initializeDefBlock(SILBasicBlock *defBB) {
217249
liveBlocks.initializeDefBlock(defBB);
218250
}
@@ -228,6 +260,9 @@ class PrunedLiveness {
228260
/// Returns false if this cannot be done.
229261
bool updateForBorrowingOperand(Operand *op);
230262

263+
/// Update this liveness to extend across the given liveness.
264+
void extendAcrossLiveness(PrunedLiveness &otherLiveness);
265+
231266
PrunedLiveBlocks::IsLive getBlockLiveness(SILBasicBlock *bb) const {
232267
return liveBlocks.getBlockLiveness(bb);
233268
}
@@ -245,7 +280,49 @@ class PrunedLiveness {
245280

246281
/// Return true if \p inst occurs before the liveness boundary. Used when the
247282
/// client already knows that inst occurs after the start of liveness.
248-
bool isWithinBoundary(SILInstruction *inst);
283+
bool isWithinBoundary(SILInstruction *inst) const;
284+
285+
bool areUsesWithinBoundary(ArrayRef<Operand *> uses,
286+
DeadEndBlocks &deadEndBlocks) const;
287+
};
288+
289+
/// Record the last use points and CFG edges that form the boundary of
290+
/// PrunedLiveness.
291+
struct PrunedLivenessBoundary {
292+
SmallVector<SILInstruction *, 8> lastUsers;
293+
SmallVector<SILBasicBlock *, 8> boundaryEdges;
294+
295+
/// Visit the point at which a lifetime-ending instruction must be inserted,
296+
/// excluding dead-end blocks. This is only useful when it is known that none
297+
/// of the lastUsers ends the lifetime, for example when creating a new borrow
298+
/// scope to enclose all uses.
299+
void visitInsertionPoints(
300+
llvm::function_ref<void(SILBasicBlock::iterator insertPt)> visitor,
301+
DeadEndBlocks *deBlocks = nullptr);
302+
303+
/// Compute the boundary from the blocks discovered during liveness analysis.
304+
///
305+
/// Precondition: \p liveness.getDiscoveredBlocks() is a valid list of all
306+
/// live blocks with no duplicates.
307+
///
308+
/// The computed boundary will completely post-dominate, including dead end
309+
/// paths. The client should query DeadEndBlocks to ignore those dead end
310+
/// paths.
311+
void compute(const PrunedLiveness &liveness);
312+
313+
/// Compute the boundary from a backward CFG traversal from a known set of
314+
/// jointly post-dominating blocks. Avoids the need to record an ordered list
315+
/// of live blocks during liveness analysis. It's ok if postDomBlocks has
316+
/// duplicates or extraneous blocks, as long as they jointly post-dominate all
317+
/// live blocks that aren't on dead-end paths.
318+
///
319+
/// If the jointly post-dominating destroys do not include dead end paths,
320+
/// then any uses on those paths will not be included in the boundary. The
321+
/// resulting partial boundary will have holes along those paths. The dead end
322+
/// successors of blocks in this live set on are not necessarilly identified
323+
/// by DeadEndBlocks.
324+
void compute(const PrunedLiveness &liveness,
325+
ArrayRef<SILBasicBlock *> postDomBlocks);
249326
};
250327

251328
} // namespace swift

lib/SIL/Utils/PrunedLiveness.cpp

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "swift/SIL/PrunedLiveness.h"
14+
#include "swift/SIL/BasicBlockDatastructures.h"
15+
#include "swift/SIL/BasicBlockUtils.h"
1416
#include "swift/SIL/OwnershipUtils.h"
1517

1618
using namespace swift;
@@ -117,7 +119,14 @@ bool PrunedLiveness::updateForBorrowingOperand(Operand *op) {
117119
return true;
118120
}
119121

120-
bool PrunedLiveness::isWithinBoundary(SILInstruction *inst) {
122+
void PrunedLiveness::extendAcrossLiveness(PrunedLiveness &otherLivesness) {
123+
// update this liveness for all the interesting users in otherLivesness.
124+
for (std::pair<SILInstruction *, bool> userAndEnd : otherLivesness.users) {
125+
updateForUse(userAndEnd.first, userAndEnd.second);
126+
}
127+
}
128+
129+
bool PrunedLiveness::isWithinBoundary(SILInstruction *inst) const {
121130
SILBasicBlock *block = inst->getParent();
122131
switch (getBlockLiveness(block)) {
123132
case PrunedLiveBlocks::Dead:
@@ -141,3 +150,116 @@ bool PrunedLiveness::isWithinBoundary(SILInstruction *inst) {
141150
}
142151
return false;
143152
}
153+
154+
bool PrunedLiveness::areUsesWithinBoundary(ArrayRef<Operand *> uses,
155+
DeadEndBlocks &deadEndBlocks) const {
156+
for (auto *use : uses) {
157+
auto *user = use->getUser();
158+
if (!isWithinBoundary(user) && !deadEndBlocks.isDeadEnd(user->getParent()))
159+
return false;
160+
}
161+
return true;
162+
}
163+
164+
void PrunedLivenessBoundary::visitInsertionPoints(
165+
llvm::function_ref<void(SILBasicBlock::iterator insertPt)> visitor,
166+
DeadEndBlocks *deBlocks) {
167+
for (SILInstruction *user : lastUsers) {
168+
if (!isa<TermInst>(user)) {
169+
visitor(std::next(user->getIterator()));
170+
continue;
171+
}
172+
auto *predBB = user->getParent();
173+
for (SILBasicBlock *succ : predBB->getSuccessors()) {
174+
if (deBlocks && deBlocks->isDeadEnd(succ))
175+
continue;
176+
177+
assert(succ->getSinglePredecessorBlock() == predBB);
178+
visitor(succ->begin());
179+
}
180+
}
181+
for (SILBasicBlock *edge : boundaryEdges) {
182+
if (deBlocks && deBlocks->isDeadEnd(edge))
183+
continue;
184+
185+
visitor(edge->begin());
186+
}
187+
}
188+
189+
// Use \p liveness to find the last use in \p bb and add it to \p
190+
// boundary.lastUsers.
191+
static void findLastUserInBlock(SILBasicBlock *bb,
192+
PrunedLivenessBoundary &boundary,
193+
const PrunedLiveness &liveness) {
194+
for (auto instIter = bb->rbegin(), endIter = bb->rend(); instIter != endIter;
195+
++instIter) {
196+
auto *inst = &*instIter;
197+
if (liveness.isInterestingUser(inst) == PrunedLiveness::NonUser)
198+
continue;
199+
200+
boundary.lastUsers.push_back(inst);
201+
}
202+
llvm_unreachable("No user in LiveWithin block");
203+
}
204+
205+
void PrunedLivenessBoundary::compute(const PrunedLiveness &liveness) {
206+
for (SILBasicBlock *bb : liveness.getDiscoveredBlocks()) {
207+
// Process each block that has not been visited and is not LiveOut.
208+
switch (liveness.getBlockLiveness(bb)) {
209+
case PrunedLiveBlocks::LiveOut:
210+
for (SILBasicBlock *succBB : bb->getSuccessors()) {
211+
if (liveness.getBlockLiveness(succBB) == PrunedLiveBlocks::Dead) {
212+
boundaryEdges.push_back(succBB);
213+
}
214+
}
215+
break;
216+
case PrunedLiveBlocks::LiveWithin: {
217+
// The liveness boundary is inside this block. Insert a final destroy
218+
// inside the block if it doesn't already have one.
219+
findLastUserInBlock(bb, *this, liveness);
220+
break;
221+
}
222+
case PrunedLiveBlocks::Dead:
223+
llvm_unreachable("All discovered blocks must be live");
224+
}
225+
}
226+
}
227+
228+
void PrunedLivenessBoundary::compute(const PrunedLiveness &liveness,
229+
ArrayRef<SILBasicBlock *> postDomBlocks) {
230+
if (postDomBlocks.empty())
231+
return; // all paths must be dead-ends or infinite loops
232+
233+
BasicBlockWorklist blockWorklist(postDomBlocks[0]->getParent());
234+
235+
// Visit each post-dominating block as the starting point for a
236+
// backward CFG traversal.
237+
for (auto *bb : postDomBlocks) {
238+
blockWorklist.push(bb);
239+
}
240+
while (auto *bb = blockWorklist.pop()) {
241+
// Process each block that has not been visited and is not LiveOut.
242+
switch (liveness.getBlockLiveness(bb)) {
243+
case PrunedLiveBlocks::LiveOut:
244+
// A lifetimeEndBlock may be determined to be LiveOut after analyzing the
245+
// extended liveness. It is irrelevent for finding the boundary.
246+
break;
247+
case PrunedLiveBlocks::LiveWithin: {
248+
// The liveness boundary is inside this block. Insert a final destroy
249+
// inside the block if it doesn't already have one.
250+
findLastUserInBlock(bb, *this, liveness);
251+
break;
252+
}
253+
case PrunedLiveBlocks::Dead:
254+
// Continue searching upward to find the pruned liveness boundary.
255+
for (auto *predBB : bb->getPredecessorBlocks()) {
256+
if (liveness.getBlockLiveness(predBB) == PrunedLiveBlocks::LiveOut) {
257+
boundaryEdges.push_back(bb);
258+
} else {
259+
blockWorklist.pushIfNotVisited(predBB);
260+
}
261+
}
262+
break;
263+
}
264+
}
265+
}

0 commit comments

Comments
 (0)