Skip to content

Commit bab1997

Browse files
committed
Add a PrunedLiveness utility.
This bare-bones utility will be the basis for CanonicalizeOSSALifetime. It is maximally flexible and can be adopted by any analysis that needs SSA-based liveness expressed in terms of the live blocks. It's meant to be layered underneath various higher-level analyses. We could consider revamping ValueLifetimeAnalysis and layering it on top of this. If PrunedLiveness is adopted widely enough, we can combine it with a block numbering analysis so we can micro-optimize the internal data structures.
1 parent e985d97 commit bab1997

File tree

3 files changed

+335
-0
lines changed

3 files changed

+335
-0
lines changed
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
//===--- PrunedLiveness.hpp - Compute liveness from selected uses ---------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
///
13+
/// Incrementally compute and represent basic block liveness of a single live
14+
/// range. The live range is defined by points in the CFG, independent of any
15+
/// particular SSA value. The client initializes liveness with a set of
16+
/// definition blocks, typically a single block. The client then incrementally
17+
/// updates liveness by providing a set of "interesting" uses one at a time.
18+
///
19+
/// This supports discovery of pruned liveness during control flow traversal. It
20+
/// is not tied to a single SSA value and allows the client to select
21+
/// interesting uses while ignoring other uses.
22+
///
23+
/// The PrunedLiveBlocks result maps each block to its current liveness state:
24+
/// Dead, LiveWithin, LiveOut.
25+
///
26+
/// A LiveWithin block has a liveness boundary within the block. The client can
27+
/// determine the boundary's intruction position by searching for the last use.
28+
///
29+
/// LiveOut indicates that liveness extends into a successor edges, therefore,
30+
/// no uses within that block can be on the liveness boundary, unless that use
31+
/// occurs before a def in the same block.
32+
///
33+
/// All blocks are initially assumed Dead. Initializing a definition block marks
34+
/// that block LiveWithin. Each time an interesting use is discovered, blocks
35+
/// liveness may undergo one of these transitions:
36+
///
37+
/// - Dead -> LiveWithin
38+
/// - Dead -> LiveOut
39+
/// - LiveWithin -> LiveOut
40+
///
41+
/// Example 1. Local liveness.
42+
///
43+
/// -----
44+
/// | | [Dead]
45+
/// -----
46+
/// |
47+
/// -----
48+
/// | Def | [LiveWithin]
49+
/// | Use |
50+
/// -----
51+
/// |
52+
/// -----
53+
/// | | [Dead]
54+
/// -----
55+
///
56+
/// Example 2. Cross-block liveness.
57+
///
58+
/// Initial State:
59+
///
60+
/// -----
61+
/// | Def | [LiveOut]
62+
/// -----
63+
/// |
64+
/// -----
65+
/// | | [Dead]
66+
/// -----
67+
/// |
68+
/// -----
69+
/// | | [Dead]
70+
/// -----
71+
///
72+
/// State after updateForUse:
73+
///
74+
/// -----
75+
/// | Def | [LiveOut]
76+
/// -----
77+
/// |
78+
/// -----
79+
/// | | [LiveOut]
80+
/// -----
81+
/// |
82+
/// -----
83+
/// | Use | [LiveWithin]
84+
/// -----
85+
///
86+
//===----------------------------------------------------------------------===//
87+
88+
#ifndef SWIFT_SILOPTIMIZER_UTILS_PRUNEDLIVENESS_H
89+
#define SWIFT_SILOPTIMIZER_UTILS_PRUNEDLIVENESS_H
90+
91+
#include "swift/SIL/SILBasicBlock.h"
92+
93+
#ifdef NDEBUG
94+
#define SWIFT_ASSERT_ONLY_MEMBER(X)
95+
#define SWIFT_ASSERT_ONLY(X) do { } while (false)
96+
#else
97+
#define SWIFT_ASSERT_ONLY_MEMBER(X) X
98+
#define SWIFT_ASSERT_ONLY(X) do { X; } while (false)
99+
#endif
100+
101+
namespace swift {
102+
103+
/// Discover "pruned" liveness for an arbitrary set of uses. The client builds
104+
/// liveness by first initializing "def" blocks, then incrementally feeding uses
105+
/// to updateForUse().
106+
///
107+
/// For SSA live ranges, a single "def" block will dominate all uses. If no def
108+
/// block is provided, liveness is computed as if defined by a function
109+
/// argument. If the client does not provide a single, dominating def block,
110+
/// then the client must at least ensure that no uses precede the first
111+
/// definition in a def block. Since this analysis does not remember the
112+
/// positions of defs, it assumes that, within a block, uses follow
113+
/// defs. Breaking this assumption will result in a "hole" in the live range in
114+
/// which the def block's predecessors incorrectly remain dead. This situation
115+
/// could be handled by adding an updateForUseBeforeFirstDef() API.
116+
///
117+
/// TODO: This can be made space-efficient if all clients can maintain a block
118+
/// numbering so liveness info can be represented as bitsets across the blocks.
119+
class PrunedLiveBlocks {
120+
public:
121+
/// Per-block liveness state computed during backward dataflow propagation.
122+
/// All unvisited blocks are considered Dead. As the are visited, blocks
123+
/// transition through these states in one direction:
124+
///
125+
/// Dead -> LiveWithin -> LiveOut
126+
///
127+
/// Dead blocks are either outside of the def's pruned liveness region, or
128+
/// they have not yet been discovered by the liveness computation.
129+
///
130+
/// LiveWithin blocks have at least one use and/or def within the block, but
131+
/// are not (yet) LiveOut.
132+
///
133+
/// LiveOut blocks are live on at least one successor path. LiveOut blocks may
134+
/// or may not contain defs or uses.
135+
enum IsLive { Dead, LiveWithin, LiveOut };
136+
137+
private:
138+
// Map all blocks in which current def is live to a flag indicating whether
139+
// the value is also liveout of the block.
140+
llvm::SmallDenseMap<SILBasicBlock *, bool, 4> liveBlocks;
141+
142+
// Once the first use has been seen, no definitions can be added.
143+
SWIFT_ASSERT_ONLY_MEMBER(bool seenUse = false);
144+
145+
public:
146+
bool empty() const { return liveBlocks.empty(); }
147+
148+
void clear() { liveBlocks.clear(); seenUse = false; }
149+
150+
void initializeDefBlock(SILBasicBlock *defBB) {
151+
assert(!seenUse && "cannot initialize more defs with partial liveness");
152+
markBlockLive(defBB, LiveWithin);
153+
}
154+
155+
/// Update this liveness result for a single use.
156+
IsLive updateForUse(Operand *use);
157+
158+
IsLive getBlockLiveness(SILBasicBlock *bb) const {
159+
auto liveBlockIter = liveBlocks.find(bb);
160+
if (liveBlockIter == liveBlocks.end())
161+
return Dead;
162+
return liveBlockIter->second ? LiveOut : LiveWithin;
163+
}
164+
165+
protected:
166+
void markBlockLive(SILBasicBlock *bb, IsLive isLive) {
167+
assert(isLive != Dead && "erasing live blocks isn't implemented.");
168+
liveBlocks[bb] = (isLive == LiveOut);
169+
}
170+
171+
void computeUseBlockLiveness(SILBasicBlock *userBB);
172+
};
173+
174+
/// PrunedLiveness tracks PrunedLiveBlocks along with "interesting" use
175+
/// points. The set of interesting uses is a superset of all uses on the
176+
/// liveness boundary. Filtering out uses that are obviously not on the liveness
177+
/// boundary improves efficiency over tracking all uses. Additionally, all
178+
/// interesting uses that are "lifetime-ending" are flagged. These uses must be
179+
/// on the liveness boundary by their nature, regardless of any other uses. It
180+
/// is up to the client to determine which uses are lifetime-ending. In OSSA,
181+
/// the lifetime-ending property might be detemined by
182+
/// OwnershipConstraint::isLifetimeEnding(). In non-OSSA, it might be determined
183+
/// by deallocation.
184+
///
185+
/// Note: unlike OwnershipLiveRange, this represents a lifetime in terms of the
186+
/// CFG boundary rather that the use set, and, because it is "pruned", it only
187+
/// includes liveness generated by select uses. For example, it does not
188+
/// necessarily include liveness up to destroy_value or end_borrow
189+
/// instructions.
190+
class PrunedLiveness {
191+
PrunedLiveBlocks liveBlocks;
192+
193+
// Map all "interesting" user instructions in this def's live range to a flag
194+
// indicating whether they must end the lifetime.
195+
//
196+
// Lifetime-ending users are always on the boundary so are always interesting.
197+
//
198+
// Non-lifetime-ending uses within a LiveWithin block are interesting because
199+
// they may be the last use in the block.
200+
//
201+
// Non-lifetime-ending within a LiveOut block are uninteresting.
202+
llvm::SmallDenseMap<SILInstruction *, bool, 8> users;
203+
204+
public:
205+
bool empty() const {
206+
assert(!liveBlocks.empty() || users.empty());
207+
return liveBlocks.empty();
208+
}
209+
210+
void clear() {
211+
liveBlocks.clear();
212+
users.clear();
213+
}
214+
215+
void initializeDefBlock(SILBasicBlock *defBB) {
216+
liveBlocks.initializeDefBlock(defBB);
217+
}
218+
219+
void updateForUse(Operand *use, bool lifetimeEnding);
220+
221+
PrunedLiveBlocks::IsLive getBlockLiveness(SILBasicBlock *bb) const {
222+
return liveBlocks.getBlockLiveness(bb);
223+
}
224+
225+
enum IsInterestingUser { NonUser, NonLifetimeEndingUse, LifetimeEndingUse };
226+
227+
/// Return a result indicating whether the given user was identified as an
228+
/// interesting use of the current def and whether it ends the lifetime.
229+
IsInterestingUser isInterestingUser(SILInstruction *user) const {
230+
auto useIter = users.find(user);
231+
if (useIter == users.end())
232+
return NonUser;
233+
return useIter->second ? LifetimeEndingUse : NonLifetimeEndingUse;
234+
}
235+
};
236+
237+
} // namespace swift
238+
239+
#endif

lib/SILOptimizer/Utils/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ target_sources(swiftSILOptimizer PRIVATE
1717
OptimizerStatsUtils.cpp
1818
PartialApplyCombiner.cpp
1919
PerformanceInlinerUtils.cpp
20+
PrunedLiveness.cpp
2021
SILInliner.cpp
2122
SILSSAUpdater.cpp
2223
SpecializationMangler.cpp
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//===--- PrunedLiveness.cpp - Compute liveness from selected uses ---------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "swift/SILOptimizer/Utils/PrunedLiveness.h"
14+
15+
using namespace swift;
16+
17+
/// Mark blocks live during a reverse CFG traversal from one specific block
18+
/// containing a user.
19+
void PrunedLiveBlocks::computeUseBlockLiveness(SILBasicBlock *userBB) {
20+
// If we are visiting this block, then it is not already LiveOut. Mark it
21+
// LiveWithin to indicate a liveness boundary within the block.
22+
markBlockLive(userBB, LiveWithin);
23+
24+
SmallVector<SILBasicBlock *, 8> predBBWorklist({userBB});
25+
while (!predBBWorklist.empty()) {
26+
SILBasicBlock *bb = predBBWorklist.pop_back_val();
27+
28+
// The popped `bb` is live; now mark all its predecessors LiveOut.
29+
//
30+
// Traversal terminates at any previously visited block, including the
31+
// blocks initialized as definition blocks.
32+
for (auto *predBB : bb->getPredecessorBlocks()) {
33+
switch (getBlockLiveness(predBB)) {
34+
case Dead:
35+
predBBWorklist.push_back(predBB);
36+
LLVM_FALLTHROUGH;
37+
case LiveWithin:
38+
markBlockLive(predBB, LiveOut);
39+
break;
40+
case LiveOut:
41+
break;
42+
}
43+
}
44+
}
45+
}
46+
47+
/// Update the current def's liveness based on one specific use operand.
48+
///
49+
/// Return the updated liveness of the \p use block (LiveOut or LiveWithin).
50+
///
51+
/// Terminators are not live out of the block.
52+
PrunedLiveBlocks::IsLive PrunedLiveBlocks::updateForUse(Operand *use) {
53+
seenUse = true;
54+
55+
auto *bb = use->getUser()->getParent();
56+
switch (getBlockLiveness(bb)) {
57+
case LiveOut:
58+
return LiveOut;
59+
case LiveWithin:
60+
return LiveWithin;
61+
case Dead: {
62+
// This use block has not yet been marked live. Mark it and its predecessor
63+
// blocks live.
64+
computeUseBlockLiveness(bb);
65+
return getBlockLiveness(bb);
66+
}
67+
}
68+
}
69+
70+
//===----------------------------------------------------------------------===//
71+
// MARK: PrunedLiveness
72+
//===----------------------------------------------------------------------===//
73+
74+
void PrunedLiveness::updateForUse(Operand *use, bool lifetimeEnding) {
75+
auto useBlockLive = liveBlocks.updateForUse(use);
76+
// Record all uses of blocks on the liveness boundary. For blocks marked
77+
// LiveWithin, the boundary is considered to be the last use in the block.
78+
if (!lifetimeEnding && useBlockLive == PrunedLiveBlocks::LiveOut) {
79+
return;
80+
}
81+
// Note that a user may use the current value from multiple operands. If any
82+
// of the uses are non-lifetime-ending, then we must consider the user
83+
// itself non-lifetime-ending; it cannot be a final destroy point because
84+
// the value of the non-lifetime-ending operand must be kept alive until the
85+
// end of the user. Consider a call that takes the same value using
86+
// different conventions:
87+
//
88+
// apply %f(%val, %val) : $(@guaranteed, @owned) -> ()
89+
//
90+
// This call is not considered the end of %val's lifetime. The @owned
91+
// argument must be copied.
92+
auto iterAndSuccess = users.try_emplace(use->getUser(), lifetimeEnding);
93+
if (!iterAndSuccess.second)
94+
iterAndSuccess.first->second &= lifetimeEnding;
95+
}

0 commit comments

Comments
 (0)