Skip to content

Commit 33bf18e

Browse files
committed
Add a utility to check for edges into dead-end regions.
This is subtly different from just checking whether the destination of an edge is dead-end, because edges *internal* to dead-end regions generally still need to be treated normally. Fundamentally, such an edge must be part of a loop.
1 parent 1fd5455 commit 33bf18e

File tree

3 files changed

+587
-0
lines changed

3 files changed

+587
-0
lines changed

include/swift/SIL/BasicBlockUtils.h

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#define SWIFT_SIL_BASICBLOCKUTILS_H
1515

1616
#include "swift/SIL/BasicBlockBits.h"
17+
#include "swift/SIL/BasicBlockData.h"
1718
#include "swift/SIL/BasicBlockDatastructures.h"
1819
#include "swift/SIL/SILValue.h"
1920
#include "llvm/ADT/SetVector.h"
@@ -125,6 +126,213 @@ class DeadEndBlocks {
125126
void propagateNewlyReachableBlocks(unsigned startIdx);
126127
};
127128

129+
/// A utility for detecting edges that enter a dead-end region.
130+
///
131+
/// A dead-end region is a strongly-connected component of the CFG
132+
/// consisting solely of dead-end blocks (i.e. from which it is not
133+
/// possible to reach a function exit). The strongly-connected
134+
/// components of a CFG form a DAG: once control flow from the entry
135+
/// block has entered an SCC, it cannot return to an earlier SCC
136+
/// (because then by definition they would have to be the same SCC).
137+
///
138+
/// Note that the interior edges of a dead-end region do not *enter*
139+
/// the region. Only edges from an earlier SCC count as edges into
140+
/// the region.
141+
///
142+
/// For example, in this CFG:
143+
///
144+
/// /-> bb1 -> bb2 -> return
145+
/// bb0
146+
/// \-> bb3 -> bb4 -> bb5 -> unreachable
147+
/// ^ |
148+
/// \------/
149+
///
150+
/// The edge from bb0 to bb3 enters a new dead-end region, as does
151+
/// the edge from bb4 to bb5. The edge from bb4 to bb3 does not
152+
/// enter a new region because it is an internal edge of its region.
153+
///
154+
/// Edges that enter dead-end regions are special in SIL because certain
155+
/// joint post-dominance rules are relaxed for them. For example, the
156+
/// stack does need not be consistent on different edges into a dead-end
157+
/// region.
158+
class DeadEndEdges {
159+
enum : unsigned {
160+
/// A region data value which represents that a block is unreachable
161+
/// from the entry block.
162+
UnreachableRegionData = 0,
163+
164+
/// A region data value which represents that a block is reachable
165+
/// from the entry block but not in a dead-end region.
166+
NonDeadEndRegionData = 1,
167+
168+
/// A value that must be added to a region index when storing it in
169+
/// a region data.
170+
///
171+
/// This should be the smallest number such that
172+
/// (IndexOffset << IndexShift)
173+
/// is always greater than all of the special region-data values
174+
/// above.
175+
IndexOffset = 1,
176+
177+
/// A mask which can be applied to a region to say that it contains
178+
/// a cycle. This slightly optimizes the check in isDeadEndEdge for
179+
/// the common case where regions do not have cycles.
180+
HasCycleMask = 0x1,
181+
182+
/// The amount to shift the region index by when storing it in a
183+
/// region data.
184+
///
185+
/// This should be the smallest number such that an arbitrary value
186+
/// left-shifted by it will not have any of the mask bits set.
187+
IndexShift = 1,
188+
};
189+
190+
/// An integer representing what we know about the SCC partition that
191+
/// a particular block is in. All blocks in the same region store the
192+
/// same value to make comparisons faster.
193+
///
194+
/// Either:
195+
/// - UnreachableRegionData, representing a block that cannot be
196+
/// reached from the entry block;
197+
/// - NonDeadEndRegionData, representing a block that can be reached
198+
/// from the entry block but is not in a dead-end region; or
199+
/// - an encoded region index, representing a block that is in a
200+
/// dead-end region.
201+
///
202+
/// A region index is a unique value in 0..<numDeadEndRegions,
203+
/// selected for a specific dead-end SCC. It is encoded by adding
204+
/// IndexOffset, left-shifting by IndexShift, and then or'ing
205+
/// in any appropriate summary bits like HasCycleMask.
206+
///
207+
/// If regionDataForBlock isn't initialized, the function contains
208+
/// no dead-end blocks.
209+
std::optional<BasicBlockData<unsigned>> regionDataForBlock;
210+
211+
/// The total number of dead-end regions in the function.
212+
unsigned numDeadEndRegions;
213+
214+
static constexpr bool isDeadEndRegion(unsigned regionData) {
215+
return regionData >= (IndexOffset << IndexShift);
216+
}
217+
218+
static unsigned getIndexFromRegionData(unsigned regionData) {
219+
assert(isDeadEndRegion(regionData));
220+
return (regionData >> IndexShift) - IndexOffset;
221+
}
222+
223+
public:
224+
/// Perform the analysis on the given function. An existing
225+
/// DeadEndBlocks analysis can be passed in to avoid needing to
226+
/// compute it anew.
227+
explicit DeadEndEdges(SILFunction *F,
228+
DeadEndBlocks *deadEndBlocks = nullptr);
229+
230+
/// Return the number of dead-end regions in the function.
231+
unsigned getNumDeadEndRegions() const {
232+
return numDeadEndRegions;
233+
}
234+
235+
/// Does the given CFG edge enter a new dead-end region?
236+
///
237+
/// If so, return the index of the dead-end region it enters.
238+
std::optional<unsigned>
239+
entersDeadEndRegion(SILBasicBlock *srcBB, SILBasicBlock *dstBB) const {
240+
// If we didn't initialize regionDataForBlock, there are no dead-end
241+
// edges at all.
242+
if (!regionDataForBlock)
243+
return std::nullopt;
244+
245+
auto dstRegionData = (*regionDataForBlock)[dstBB];
246+
247+
// If the destination block is not in a dead-end region, this is
248+
// not a dead-end edge.
249+
if (!isDeadEndRegion(dstRegionData)) return std::nullopt;
250+
251+
unsigned dstRegionIndex = getIndexFromRegionData(dstRegionData);
252+
253+
// If the destination block is in a region with no cycles, every edge
254+
// to it is a dead-end edge; no need to look up the source block's
255+
// region.
256+
if (!(dstRegionData & HasCycleMask)) return dstRegionIndex;
257+
258+
// Otherwise, it's a dead-end edge if the source block is in a
259+
// different region. (That region may or may not be itself be a
260+
// dead-end region.)
261+
auto srcRegionData = (*regionDataForBlock)[srcBB];
262+
if (srcRegionData != dstRegionData) {
263+
return dstRegionIndex;
264+
} else {
265+
return std::nullopt;
266+
}
267+
}
268+
269+
/// A helper class for tracking visits to edges into dead-end regions.
270+
///
271+
/// The client is assumed to be doing a walk of the function which will
272+
/// naturally visit each edge exactly once. This set allows the client
273+
/// to track when they've processed every edge to a particular dead-end
274+
/// region and can therefore safely enter it.
275+
///
276+
/// The set does not count edges from unreachable blocks by default. This
277+
/// matches the normal expectation that the client is doing a CFG search
278+
/// and won't try to visit edges from unreachable blocks. If you are
279+
/// walking the function in some other, e.g. by iterating the blocks,
280+
/// you must pass `true` for `includeUnreachableEdges`.
281+
class VisitingSet {
282+
const DeadEndEdges &edges;
283+
284+
/// Stores the remaining number of edges for each dead-end region
285+
/// in the function.
286+
SmallVector<unsigned> remainingEdgesForRegion;
287+
288+
friend class DeadEndEdges;
289+
explicit VisitingSet(const DeadEndEdges &parent,
290+
bool includeUnreachableEdges);
291+
292+
public:
293+
/// Record that a dead-end edge to the given block was visited.
294+
///
295+
/// Returns true if this was the last dead-end edge to the region
296+
/// containing the block.
297+
///
298+
/// Do not call this multiple times for the same edge. Do not
299+
/// call this for an unreachable edge if you did not create the
300+
/// set including unreachable edges.
301+
bool visitEdgeTo(SILBasicBlock *destBB) {
302+
assert(edges.regionDataForBlock &&
303+
"visiting dead-end edge in function that has none");
304+
auto destRegionData = (*edges.regionDataForBlock)[destBB];
305+
assert(isDeadEndRegion(destRegionData) &&
306+
"destination block is not in a dead-end region");
307+
308+
auto destRegionIndex = getIndexFromRegionData(destRegionData);
309+
assert(remainingEdgesForRegion[destRegionIndex] > 0 &&
310+
"no remaining dead-end edges for region; visited "
311+
"multiple times?");
312+
313+
auto numRemaining = --remainingEdgesForRegion[destRegionIndex];
314+
return numRemaining == 0;
315+
}
316+
317+
/// Return true if all of the edges have been visited.
318+
bool visitedAllEdges() const {
319+
for (auto count : remainingEdgesForRegion) {
320+
if (count) return false;
321+
}
322+
return true;
323+
}
324+
};
325+
326+
/// Create a counter set which can be used to count edges in the
327+
/// dead-end regions.
328+
///
329+
/// By default, the set does not include edges from unreachable blocks.
330+
VisitingSet createVisitingSet(bool includeUnreachableEdges = false) const {
331+
return VisitingSet(*this, includeUnreachableEdges);
332+
}
333+
};
334+
335+
128336
/// Compute joint-postdominating set for \p dominatingBlock and \p
129337
/// dominatedBlockSet found by walking up the CFG from the latter to the
130338
/// former.

0 commit comments

Comments
 (0)