mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
960 lines
36 KiB
C++
960 lines
36 KiB
C++
//===--- Reachability.h ---------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// Reachability data flow analysis using a path-discovery worklist. For
|
|
/// efficient data flow propagation based on a single SSA value and its uses.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SWIFT_SILOPTIMIZER_ANALYSIS_REACHABILITY_H
|
|
#define SWIFT_SILOPTIMIZER_ANALYSIS_REACHABILITY_H
|
|
|
|
#include "swift/SIL/BasicBlockBits.h"
|
|
#include "swift/SIL/BasicBlockDatastructures.h"
|
|
#include "swift/SIL/SILBasicBlock.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/SILSuccessor.h"
|
|
#include "llvm/ADT/PointerUnion.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
namespace swift {
|
|
|
|
/// Pessimistic, non-iterative data flow for analyzing backward reachability
|
|
/// from a set of last uses to their dominating def or nearest barrier.
|
|
///
|
|
/// Intended for frequently called utilities where minimizing the cost of data
|
|
/// flow is more important than analyzing reachability across loops. Expected to
|
|
/// visit very few blocks because barriers often occur close to a last use.
|
|
///
|
|
/// BlockReachability {
|
|
/// // True if the beginning of \p block is reachable.
|
|
/// // Typically a BasicBlockSet wrapper.
|
|
/// bool hasReachableBegin(SILBasicBlock *block);
|
|
///
|
|
/// // Mark the beginning of a block reachable. Only called once per block.
|
|
/// // Typically a BasicBlockSet wrapper.
|
|
/// void markReachableBegin(SILBasicBlock *block);
|
|
///
|
|
/// // Mark the end of a block reachable. Only called once per block.
|
|
/// // Typically a BasicBlockSet wrapper.
|
|
/// void markReachableEnd(SILBasicBlock *block);
|
|
///
|
|
/// // Return true if \p inst is a barrier. Called once for each reachable
|
|
/// // instruction, assuming that each lastUsePoint is itself a barrier.
|
|
/// // Used by the data flow client to perform additional book-keeping,
|
|
/// // such as recording debug_value instructions.
|
|
/// bool checkReachableBarrier(SILInstruction *inst);
|
|
///
|
|
/// // Return true if any of \p block's phis are barriers.
|
|
/// bool checkReachablePhiBarrier(SILBasicBlock *block);
|
|
/// };
|
|
template <typename BlockReachability>
|
|
class BackwardReachability {
|
|
SILFunction *function;
|
|
BlockReachability &reachableBlocks;
|
|
BasicBlockWorklist cfgWorklist;
|
|
bool solved = false;
|
|
|
|
public:
|
|
BackwardReachability(SILFunction *function,
|
|
BlockReachability &reachableBlocks)
|
|
: function(function), reachableBlocks(reachableBlocks),
|
|
cfgWorklist(function) {}
|
|
|
|
// Initialize data flow starting points before running solveBackward.
|
|
void initLastUse(SILInstruction *lastUsePoint) {
|
|
assert(!solved && "adding last use after solving!?");
|
|
auto *lastUseBlock = lastUsePoint->getParent();
|
|
auto *previous = lastUsePoint->getPreviousInstruction();
|
|
if (!previous || canReachBlockBegin(previous)) {
|
|
pushPreds(lastUseBlock);
|
|
}
|
|
}
|
|
|
|
// Data flow "meet": intersection of successor reachability.
|
|
void solveBackward() {
|
|
while (SILBasicBlock *block = cfgWorklist.popAndForget()) {
|
|
if (!meetOverSuccessors(block))
|
|
continue;
|
|
|
|
reachableBlocks.markReachableEnd(block);
|
|
|
|
if (canReachBlockBegin(block->getTerminator())) {
|
|
pushPreds(block);
|
|
}
|
|
}
|
|
solved = true;
|
|
}
|
|
|
|
protected:
|
|
BackwardReachability(BackwardReachability const &) = delete;
|
|
BackwardReachability &operator=(BackwardReachability const &) = delete;
|
|
|
|
// Perform a "meet" over successor begin reachability.
|
|
// Return true if \p predecessor's end is pessimistically reachable.
|
|
//
|
|
// Meet:
|
|
// ReachableEnd(predecessor) := intersection(ReachableBegin, successors)
|
|
bool meetOverSuccessors(SILBasicBlock *block) {
|
|
return llvm::all_of(block->getSuccessorBlocks(), [this](auto *successor) {
|
|
return reachableBlocks.hasReachableBegin(successor);
|
|
});
|
|
}
|
|
|
|
// Local data flow. Computes the block's flow function.
|
|
bool canReachBlockBegin(SILInstruction *lastReachablePoint) {
|
|
do {
|
|
if (reachableBlocks.checkReachableBarrier(lastReachablePoint))
|
|
return false;
|
|
lastReachablePoint = lastReachablePoint->getPreviousInstruction();
|
|
} while (lastReachablePoint);
|
|
return true;
|
|
}
|
|
|
|
// Propagate global data flow from \p succBB to its predecessors.
|
|
void pushPreds(SILBasicBlock *succBB) {
|
|
if (succBB->hasPhi())
|
|
if (reachableBlocks.checkReachablePhiBarrier(succBB))
|
|
return;
|
|
|
|
reachableBlocks.markReachableBegin(succBB);
|
|
|
|
for (SILBasicBlock *predBB : succBB->getPredecessorBlocks()) {
|
|
cfgWorklist.pushIfNotVisited(predBB);
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Iterative, optimistic data flow for analyzing backward reachability from a
|
|
/// set of gens to their dominating def or nearest barrier.
|
|
///
|
|
/// Uses a worklist approach to avoid extraneous iteration. Additionally,
|
|
/// avoids analyzing reachability in blocks which are known to be unreachable.
|
|
///
|
|
/// interface Effects {
|
|
/// /// The effect, if any, of the specified instruction.
|
|
/// Effect effectForInstruction(SILInstruction *);
|
|
///
|
|
/// /// The effect, if any, of the phis of the specified block.
|
|
/// Effect effectForPhi(SILBasicBlock *);
|
|
///
|
|
/// /// The uses from which reachability will begin.
|
|
/// iterable gens();
|
|
/// }
|
|
template <typename Effects>
|
|
class IterativeBackwardReachability final {
|
|
public:
|
|
/// How an instruction or phi can modify the state of a block.
|
|
///
|
|
/// NoEffect - the state is unmodified
|
|
/// Kill - the state becomes unreachable
|
|
/// Gen - the state becomes reachable
|
|
struct Effect final {
|
|
enum class Value {
|
|
NoEffect,
|
|
Kill,
|
|
Gen,
|
|
};
|
|
Value value;
|
|
|
|
Effect(Value value) : value(value) {}
|
|
Effect() : value(Value::NoEffect) {}
|
|
|
|
static Effect NoEffect() { return {Value::NoEffect}; }
|
|
static Effect Kill() { return {Value::Kill}; }
|
|
static Effect Gen() { return {Value::Gen}; }
|
|
|
|
bool isNoEffect() const { return value == Value::NoEffect; }
|
|
|
|
/// Form the effect equivalent to first applying this effect and then
|
|
/// applying the specified effect.
|
|
Effect composing(Effect other) const {
|
|
return other.isNoEffect() ? *this : other;
|
|
}
|
|
|
|
explicit operator bool() const { return !isNoEffect(); }
|
|
bool operator==(const Effect &other) const { return value == other.value; }
|
|
bool operator!=(const Effect &other) const { return value != other.value; }
|
|
};
|
|
|
|
/// How reachable a point in the function is:
|
|
/// - Unreachable: Some path from a gen to here passes through a kill.
|
|
/// - Reachable: No path from a gen point to here passes through a kill.
|
|
/// - Unknown: This point is the unexplored end of a block which contains a
|
|
/// gen.
|
|
///
|
|
/// These states form a simple lattice:
|
|
///
|
|
/// Unknown (top)
|
|
/// |
|
|
/// Reachable
|
|
/// |
|
|
/// Unreachable (bottom)
|
|
///
|
|
/// During initialization, certain points may be set to have the Unknown
|
|
/// state. After that point, all blocks which have been discovered are
|
|
/// optimistically assumed to have Reachable begin and end states until it is
|
|
/// recorded otherwise.
|
|
struct State final {
|
|
enum class Value {
|
|
Unreachable,
|
|
Reachable,
|
|
Unknown,
|
|
};
|
|
Value value;
|
|
|
|
static State Unreachable() { return {Value::Unreachable}; }
|
|
static State Reachable() { return {Value::Reachable}; }
|
|
static State Unknown() { return {Value::Unknown}; }
|
|
State meet(State other) { return value < other.value ? *this : other; }
|
|
State apply(Effect effect) {
|
|
switch (effect.value) {
|
|
case Effect::Value::NoEffect:
|
|
return *this;
|
|
case Effect::Value::Kill:
|
|
return Unreachable();
|
|
case Effect::Value::Gen:
|
|
return Reachable();
|
|
};
|
|
};
|
|
bool operator==(const State &other) { return value == other.value; }
|
|
bool operator!=(const State &other) { return value != other.value; }
|
|
};
|
|
|
|
/// The output of the dataflow.
|
|
class Result final {
|
|
/// Blocks whose instructions+phis are summarized as gen.
|
|
BasicBlockSet genBlocks;
|
|
/// Blocks whose instructions+phis are summarized as kill.
|
|
BasicBlockSet killBlocks;
|
|
/// Blocks whose ends are in the ::Unreachable state.
|
|
BasicBlockSet unreachableEndBlocks;
|
|
/// Blocks whose ends are in the ::Unknown state.
|
|
BasicBlockSet unknownEndBlocks;
|
|
|
|
public:
|
|
/// The blocks found between the gens and the initialBlocks into which
|
|
/// reachability may extend.
|
|
BasicBlockSetVector discoveredBlocks;
|
|
/// The sublist of gens which are killed within the blocks where they occur.
|
|
llvm::SmallSetVector<SILInstruction *, 32> localGens;
|
|
|
|
/// Construct a result for running IterativeBackwardReachability in a given
|
|
/// function.
|
|
Result(SILFunction *function)
|
|
: genBlocks(function), killBlocks(function),
|
|
unreachableEndBlocks(function), unknownEndBlocks(function),
|
|
discoveredBlocks(function){};
|
|
|
|
/// The previously recorded end state of the specified block.
|
|
State getEndStateForBlock(SILBasicBlock *block);
|
|
|
|
/// The begin state of the specified block.
|
|
State getBeginStateForBlock(SILBasicBlock *block) {
|
|
auto endState = getEndStateForBlock(block);
|
|
return transferEndToBegin(block, endState);
|
|
}
|
|
|
|
/// The summary effect of the specified block on reachability, as previously
|
|
/// recorded.
|
|
Effect getEffectForBlock(SILBasicBlock *block);
|
|
|
|
/// Record the summary effect of the specified block.
|
|
template <bool SkipOverwrite>
|
|
void setEffectForBlock(SILBasicBlock *block, Effect effect);
|
|
|
|
/// Transfer the specified block's provided end state to its begin state by
|
|
/// applying its effect.
|
|
State transferEndToBegin(SILBasicBlock *block, State state) {
|
|
auto effect = getEffectForBlock(block);
|
|
return state.apply(effect);
|
|
}
|
|
|
|
/// During initialization, set the specified block's initial state to be
|
|
/// ::Unknown.
|
|
void initializeEndStateToUnknown(SILBasicBlock *block) {
|
|
unknownEndBlocks.insert(block);
|
|
}
|
|
|
|
/// Record a transition (down-lattice) of the indicated block to the
|
|
/// specified state.
|
|
bool setEndStateforBlock(SILBasicBlock *block, State state);
|
|
|
|
private:
|
|
Result(Result const &) = delete;
|
|
Result &operator=(Result const &) = delete;
|
|
};
|
|
|
|
/// Construct a dataflow for the specified function to run from the gens
|
|
/// provided by \p effects to the specified \p initialBlocks. So that the
|
|
/// result structure can be owned by the caller, it is taken by reference
|
|
/// here.
|
|
///
|
|
/// If \p initialBlocks is empty, the dataflow may run up to the begin of the
|
|
/// function.
|
|
IterativeBackwardReachability(SILFunction *function,
|
|
ArrayRef<SILBasicBlock *> initialBlocks,
|
|
Effects &effects, Result &result)
|
|
: function(function), initialBlocks(function), effects(effects),
|
|
result(result), dataflowWorklist(function) {
|
|
for (auto *block : initialBlocks) {
|
|
this->initialBlocks.insert(block);
|
|
}
|
|
}
|
|
|
|
/// Convenience constructor to pass a single initial block.
|
|
static IterativeBackwardReachability
|
|
untilInitialBlock(SILFunction *function, SILBasicBlock *initialBlock,
|
|
Effects &effects, Result &result) {
|
|
using InitialBlocks = ArrayRef<SILBasicBlock *>;
|
|
InitialBlocks initialBlocks =
|
|
initialBlock ? InitialBlocks(initialBlock) : InitialBlocks();
|
|
return IterativeBackwardReachability(function, initialBlocks, effects,
|
|
result);
|
|
}
|
|
|
|
/// Step 1: Prepare to run the global dataflow: discover and summarize the
|
|
/// blocks in the relevant region.
|
|
///
|
|
/// Effects are queried in order to summarize.
|
|
void initialize();
|
|
|
|
/// Step 2 (Optional): Add kills beyond those that were already added during
|
|
/// initialization, requerying effects as needed.
|
|
void addKill(SILInstruction *instruction);
|
|
|
|
/// Step 3: Propagate reachability through the blocks in the identified
|
|
/// region.
|
|
void solve();
|
|
|
|
/// Step 4 (Optional): Visit each barrier instruction, phi, and block.
|
|
///
|
|
/// Effects are requeried.
|
|
///
|
|
/// A barrier block here means the target block of a barrier edge.
|
|
///
|
|
/// interface Visitor {
|
|
/// void visitBarrierInstruction(SILInstruction *)
|
|
/// void visitBarrierPhi(SILBasicBlock *)
|
|
/// void visitBarrierBlock(SILBasicBlock *)
|
|
/// void visitInitialBlock(SILBasicBlock *)
|
|
/// }
|
|
template <typename Visitor>
|
|
void findBarriers(Visitor &visitor);
|
|
|
|
private:
|
|
/// How far along the dataflow is.
|
|
enum class Stage {
|
|
/// The dataflow has not done any work.
|
|
Unstarted,
|
|
/// Performing local, sub-block dataflow from gens, determining which gens
|
|
/// are local to and should be stored in \p localGens.
|
|
Initializing,
|
|
/// Discovering blocks into which reachability may extend and lazily doing
|
|
/// local dataflows of those blocks as they are seen.
|
|
SolvingLocal,
|
|
/// Discovery has completed and local dataflow has been performed for all
|
|
/// discovered blocks. At this point, clients are free to examine the
|
|
/// Result's \p discoveredBlocks add more kills.
|
|
SolvedLocal,
|
|
/// Running the iterative dataflow, draining the worklist of blocks which
|
|
/// are unreachable-at-begin.
|
|
SolvingGlobal,
|
|
/// The dataflow has finished.
|
|
SolvedGlobal,
|
|
};
|
|
|
|
/// The function in which the dataflow will run.
|
|
SILFunction *function;
|
|
/// The blocks beyond which the dataflow will not propagate.
|
|
BasicBlockSet initialBlocks;
|
|
|
|
/// Input to the dataflow.
|
|
Effects &effects;
|
|
/// Output from the dataflow.
|
|
Result &result;
|
|
|
|
/// The worklist for the global data flow. It consists of blocks whose begin
|
|
/// states have been found to be unreachable; the unreachable-at-begin-ness of
|
|
/// each such block is to be propagated into unreachable-at-end-ness of its
|
|
/// predecessors.
|
|
BasicBlockWorklist dataflowWorklist;
|
|
/// Current activity of the dataflow.
|
|
Stage stage = Stage::Unstarted;
|
|
|
|
/// Whether dataflow continues beyond this block.
|
|
bool stopAtBlock(SILBasicBlock *block) {
|
|
return initialBlocks.contains(block) || &*function->begin() == block;
|
|
}
|
|
|
|
/// Form the meet of the end state of the provided predecessor with the begin
|
|
/// states of all its successors.
|
|
State meetOverSuccessors(SILBasicBlock *predecessor);
|
|
|
|
/// Whether the first (non-NoEffect) effect before the specified instruction
|
|
/// is of the indicated kind.
|
|
bool findLocalEffectBefore(SILInstruction *from, Effect target);
|
|
|
|
/// Summarize the effect of the block.
|
|
Effect summarizeLocalEffect(SILBasicBlock *block);
|
|
|
|
/// Carry out the portion of initialization corresponding to a single gen.
|
|
void initializeFromGen(SILInstruction *gen);
|
|
|
|
/// Propagates the unreachable-at-begin-ness of the specified block into
|
|
/// unreachable-at-end-ness of its predecessors.
|
|
void propagateIntoPredecessors(SILBasicBlock *successor);
|
|
|
|
/// Find and visit a single barrier instruction, phi, or block. Returns true
|
|
/// if one is found.
|
|
///
|
|
/// A barrier block here means the target block of a barrier edge.
|
|
///
|
|
/// interface Visitor {
|
|
/// void visitBarrierInstruction(SILInstruction *)
|
|
/// void visitBarrierPhi(SILBasicBlock *)
|
|
/// void visitBarrierBlock(SILBasicBlock *)
|
|
/// void visitInitialBlock(SILBasicBlock *)
|
|
/// }
|
|
template <typename Visitor>
|
|
bool findBarrier(SILInstruction *from, SILBasicBlock *block,
|
|
Visitor &visitor);
|
|
};
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: IterativeBackwardReachability - Initialization
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Prepare the dataflow to run.
|
|
///
|
|
/// Simultaneously (1) discover the blocks which could be reached and within
|
|
/// which consequently the data flow must run and (2) summarize the local
|
|
/// effect of each block for use by the dataflow.
|
|
///
|
|
/// Starting from the gens, find all blocks which might be reached up to and
|
|
/// including the initialBlocks. Summarize the effects of these blocks along
|
|
/// the way.
|
|
template <typename Effects>
|
|
void IterativeBackwardReachability<Effects>::initialize() {
|
|
assert(stage == Stage::Unstarted);
|
|
stage = Stage::Initializing;
|
|
for (auto *gen : effects.gens()) {
|
|
initializeFromGen(gen);
|
|
}
|
|
stage = Stage::SolvingLocal;
|
|
/// Thanks to StackList's iterator guarantees (BasicBlockSetVector uses
|
|
/// StackList's iterator), the iterator won't be invalidated by the
|
|
/// modification that is done within the loop.
|
|
for (auto iterator = result.discoveredBlocks.begin();
|
|
iterator != result.discoveredBlocks.end(); ++iterator) {
|
|
auto *block = *iterator;
|
|
auto effect = summarizeLocalEffect(block);
|
|
result.template setEffectForBlock</*SkipOverwrite=*/true>(block, effect);
|
|
if (effect == Effect::Kill()) {
|
|
// This block is a kill, so it is unreachable at its begin. Propagate
|
|
// that unreachable-at-begin-ness to unreachable-at-end-ness of its
|
|
// predecessors, if any of them are ever discovered.
|
|
dataflowWorklist.push(block);
|
|
// This block is a kill, so dataflow can't propagate a reachable state
|
|
// through this block to its predecessors. Don't add its predecessors
|
|
// to the list of discoverable blocks--they can't be reachable by way of
|
|
// this block. Note though that they may become reachable through other
|
|
// adjacent successors.
|
|
continue;
|
|
}
|
|
if (stopAtBlock(block)) {
|
|
// If dataflow is to stop at this block, it mustn't propagate a reachable
|
|
// state through this block to its predecessors.
|
|
continue;
|
|
}
|
|
for (auto *predecessor : block->getPredecessorBlocks())
|
|
result.discoveredBlocks.insert(predecessor);
|
|
}
|
|
stage = Stage::SolvedLocal;
|
|
}
|
|
|
|
/// Initialize the dataflow for a single gen from effect.gens.
|
|
///
|
|
/// Summarize and record the local effect of the partial instruction range
|
|
/// starting at gen and running to the begin of the block.
|
|
template <typename Effects>
|
|
void IterativeBackwardReachability<Effects>::initializeFromGen(
|
|
SILInstruction *gen) {
|
|
auto *block = gen->getParent();
|
|
|
|
if (findLocalEffectBefore(gen, Effect::Kill())) {
|
|
// If there is a local kill before this gen, then it is a local gen.
|
|
result.localGens.insert(gen);
|
|
} else {
|
|
result.template setEffectForBlock</*SkipOverwrite=*/true>(block,
|
|
Effect::Gen());
|
|
// Only add blocks whose begins are reached to the list of blocks which
|
|
// must participate in the global dataflow.
|
|
result.discoveredBlocks.insert(block);
|
|
// When considering gen in isolation, we don't have enough information to
|
|
// decide whether it is reachable. It may be reachable, from some other
|
|
// gen, but it may not be. Set its state to ::Unknown, the top state of the
|
|
// lattice. During the dataflow, its state may be updated downwards in the
|
|
// lattice.
|
|
result.initializeEndStateToUnknown(block);
|
|
}
|
|
}
|
|
|
|
/// Summarize the effect of the block.
|
|
///
|
|
/// Compose all the effects in the block, starting from the last instruction and
|
|
/// working backwards through the first instruction and the phi if any.
|
|
template <typename Effects>
|
|
typename IterativeBackwardReachability<Effects>::Effect
|
|
IterativeBackwardReachability<Effects>::summarizeLocalEffect(
|
|
SILBasicBlock *block) {
|
|
Effect runningEffect;
|
|
for (auto &instruction : llvm::reverse(*block)) {
|
|
auto effect = effects.effectForInstruction(&instruction);
|
|
runningEffect = runningEffect.composing(effect);
|
|
}
|
|
if (block->hasPhi()) {
|
|
auto effect = effects.effectForPhi(block);
|
|
runningEffect = runningEffect.composing(effect);
|
|
}
|
|
return runningEffect;
|
|
}
|
|
|
|
/// Whether an effect of the indicated kind occurs before the specified
|
|
/// instruction.
|
|
///
|
|
/// Scan the effects in the block backwards from the specified instruction. If
|
|
/// any effect along the way is of the indicated kind, return true.
|
|
template <typename Effects>
|
|
bool IterativeBackwardReachability<Effects>::findLocalEffectBefore(
|
|
SILInstruction *from, Effect target) {
|
|
assert(target != Effect::NoEffect());
|
|
for (auto *instruction = from->getPreviousInstruction(); instruction;
|
|
instruction = instruction->getPreviousInstruction()) {
|
|
auto effect = effects.effectForInstruction(instruction);
|
|
if (effect == target)
|
|
return true;
|
|
}
|
|
auto *block = from->getParent();
|
|
if (block->hasPhi()) {
|
|
auto effect = effects.effectForPhi(block);
|
|
if (effect == target)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: IterativeBackwardReachability - Modification
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Record a new kill.
|
|
///
|
|
/// To be called only after initialization.
|
|
template <typename Effects>
|
|
void IterativeBackwardReachability<Effects>::addKill(
|
|
SILInstruction *instruction) {
|
|
// TODO: Trim discovered set if possible.
|
|
assert(stage == Stage::SolvedLocal);
|
|
auto *block = instruction->getParent();
|
|
|
|
// If the kill isn't even in a discovered block, it has no effect.
|
|
if (!result.discoveredBlocks.contains(block))
|
|
return;
|
|
|
|
if (result.getEffectForBlock(block) != Effect::Gen()) {
|
|
// If the block wasn't previously a gen block, without further work, we
|
|
// already know that adding a kill to the block makes it a kill block.
|
|
result.template setEffectForBlock</*SkipOverwrite=*/true>(block,
|
|
Effect::Kill());
|
|
return;
|
|
}
|
|
|
|
// The block was previously a gen block, so the new kill may or may not make
|
|
// it a kill block; it depends on whether the new kill is above the gen which
|
|
// made the block a gen. We could resummarize the block, starting from the
|
|
// new kill. As an optimization, instead, we just walk until we find a gen.
|
|
|
|
// Search for a previous gen.
|
|
if (findLocalEffectBefore(instruction, Effect::Gen()))
|
|
// If one is found, the new kill doesn't alter the block's effect.
|
|
return;
|
|
|
|
// Since no previous gen was found, the block is now a kill.
|
|
result.template setEffectForBlock</*SkipOverwrite=*/false>(block,
|
|
Effect::Kill());
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: IterativeBackwardReachability - Solving
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Iteratively solve the global dataflow.
|
|
///
|
|
/// Drain the worklist which consists of blocks each of whose begins are
|
|
/// unreachable, propagating that unreachable-at-begin-ness into
|
|
/// unreachable-at-end-ness of its predecessors.
|
|
template <typename Effects>
|
|
void IterativeBackwardReachability<Effects>::solve() {
|
|
assert(stage == Stage::SolvedLocal);
|
|
stage = Stage::SolvingGlobal;
|
|
// Now that all discovered blocks have been summarized, seed the worklist
|
|
// with those discovered blocks which we can already tell are unreachable
|
|
// at begin.
|
|
for (auto *block : result.discoveredBlocks) {
|
|
auto endState = meetOverSuccessors(block);
|
|
if (result.setEndStateforBlock(block, endState)) {
|
|
// The end state was updated. See whether that resulted in the begin
|
|
// state becoming unreachable.
|
|
auto beginState = result.getBeginStateForBlock(block);
|
|
if (beginState == State::Unreachable()) {
|
|
// The begin state is now unreachable. Add the block to the worklist
|
|
// so that its unreachable-at-begin-ness gets propagated to
|
|
// unreachable-at-end-ness of its predecessors.
|
|
dataflowWorklist.pushIfNotVisited(block);
|
|
}
|
|
}
|
|
}
|
|
// Drain the worklist.
|
|
while (auto *block = dataflowWorklist.popAndForget()) {
|
|
// Propagate block's unreachable-at-begin-ness into
|
|
// unreachable-at-end-ness of its predecessors.
|
|
propagateIntoPredecessors(block);
|
|
}
|
|
stage = Stage::SolvedGlobal;
|
|
}
|
|
|
|
/// Propagates the unreachable-at-begin-ness of the specified block into
|
|
/// unreachable-at-end-ness of its predecessors.
|
|
///
|
|
/// If that new unreachable-at-end-ness of any predecessor results in that
|
|
/// predecessor being newly unreachable at begin, add the predecessor to the
|
|
/// worklist.
|
|
///
|
|
/// Called with each block from the worklist as it is drained. A block is added
|
|
/// to the worklist only when it is known to be unreachable at begin;
|
|
/// consequently, when forming the meet of that block's begin state with its
|
|
/// predecessor's end state, we can skip requerying the block's end state--it
|
|
/// must be ::Unreachable.
|
|
template <typename Effects>
|
|
void IterativeBackwardReachability<Effects>::propagateIntoPredecessors(
|
|
SILBasicBlock *successor) {
|
|
// State isn't tracked above the blocks dataflow stops at. Don't propagate
|
|
// state changes into its predecessors.
|
|
if (stopAtBlock(successor))
|
|
return;
|
|
assert(result.getBeginStateForBlock(successor) == State::Unreachable() &&
|
|
"propagating unreachability into predecessors of block whose begin is "
|
|
"reachable?!");
|
|
for (auto *predecessor : successor->getPredecessorBlocks()) {
|
|
// Blocks are added to the worklist whenever they are found to be
|
|
// unreachable at begin. Specifically, no checks are done at that time that
|
|
// the block's predecessors are discovered. Check now.
|
|
if (!result.discoveredBlocks.contains(predecessor))
|
|
continue;
|
|
auto oldEndState = result.getEndStateForBlock(predecessor);
|
|
auto newEndState = State::Unreachable().meet(oldEndState);
|
|
if (result.setEndStateforBlock(predecessor, newEndState)) {
|
|
// The end state of predecessor was changed. Its begin state may be newly
|
|
// ::Unreachable.
|
|
if (result.getBeginStateForBlock(predecessor) == State::Unreachable()) {
|
|
// Propagate predecessor's new unreachable-at-begin-ness to
|
|
// unreachable-at-end-ness of _its_ predecessors.
|
|
dataflowWorklist.pushIfNotVisited(predecessor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Effects>
|
|
typename IterativeBackwardReachability<Effects>::State
|
|
IterativeBackwardReachability<Effects>::meetOverSuccessors(
|
|
SILBasicBlock *predecessor) {
|
|
auto state = result.getEndStateForBlock(predecessor);
|
|
for (auto *successor : predecessor->getSuccessorBlocks()) {
|
|
state = state.meet(result.getBeginStateForBlock(successor));
|
|
}
|
|
return state;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: IterativeBackwardReachability - Barrier Discovery
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Backwards walk discovered blocks and partial blocks starting at gens,
|
|
/// visiting barrier instructions, phis, and blocks.
|
|
///
|
|
/// interface Visitor {
|
|
/// /// Invoked with an instruction which is a barrier.
|
|
/// void visitBarrierInstruction(SILInstruction *)
|
|
///
|
|
/// /// Invoked with a block one of whose phi arguments is a barrier.
|
|
/// void visitBarrierPhi(SILBasicBlock *)
|
|
///
|
|
/// /// Invoked with target of an edge which is a barrier.
|
|
/// ///
|
|
/// /// Given a block P only some of whose successors S are
|
|
/// /// reachable-at-begin, an edge E from P to S is a barrier. This
|
|
/// /// function is invoked for each such edge E with the block S. Thanks
|
|
/// /// to the lack of critical edges in SIL, that uniquely identifies the
|
|
/// /// edge as the only edge to S.
|
|
/// ///
|
|
/// /// Additionally, this may be invoked with the effective def block if
|
|
/// /// no barriers are found between the gens and it.
|
|
/// void visitBarrierBlock(SILBasicBlock *)
|
|
///
|
|
/// /// Invoked with either the function entry block or one of the specified
|
|
/// /// initial blocks.
|
|
/// void visitInitialBlock(SILBasicBlock *)
|
|
/// }
|
|
template <typename Effects>
|
|
template <typename Visitor>
|
|
void IterativeBackwardReachability<Effects>::findBarriers(Visitor &visitor) {
|
|
assert(stage == Stage::SolvedGlobal);
|
|
for (auto *block : result.discoveredBlocks) {
|
|
switch (result.getEndStateForBlock(block).value) {
|
|
case State::Value::Reachable:
|
|
findBarrier(&block->back(), block, visitor);
|
|
break;
|
|
case State::Value::Unreachable:
|
|
for (auto *successor : block->getSuccessorBlocks()) {
|
|
if (result.getBeginStateForBlock(successor) == State::Reachable()) {
|
|
visitor.visitBarrierBlock(successor);
|
|
}
|
|
}
|
|
break;
|
|
case State::Value::Unknown:
|
|
// A block which contained a gen which was never overwritten by global
|
|
// data flow.
|
|
break;
|
|
}
|
|
}
|
|
for (auto *gen : effects.gens()) {
|
|
bool foundBarrier =
|
|
findBarrier(gen->getPreviousInstruction(), gen->getParent(), visitor);
|
|
assert((foundBarrier || !result.localGens.contains(gen)) &&
|
|
"local gen without in-block kill?!");
|
|
(void)foundBarrier;
|
|
}
|
|
}
|
|
|
|
/// Find a single barrier, starting from the specified instruction (nullable)
|
|
/// within the indicated block (non-nullable) and visit it.
|
|
///
|
|
/// Return whether a barrier was found.
|
|
///
|
|
/// interface Visitor {
|
|
/// /// Invoked with an instruction which is a barrier.
|
|
/// void visitBarrierInstruction(SILInstruction *)
|
|
///
|
|
/// /// Invoked with a block one of whose phi arguments is a barrier.
|
|
/// void visitBarrierPhi(SILBasicBlock *)
|
|
///
|
|
/// /// Invoked with target of an edge which is a barrier.
|
|
/// ///
|
|
/// /// Given a block P only some of whose successors S are
|
|
/// /// reachable-at-begin, an edge E from P to S is a barrier. This
|
|
/// /// function is invoked for each such edge E with the block S. Thanks
|
|
/// /// to the lack of critical edges in SIL, that uniquely identifies the
|
|
/// /// edge as the only edge to S.
|
|
/// ///
|
|
/// /// Additionally, this may be invoked with the effective def block if
|
|
/// /// no barriers are found between the gens and it.
|
|
/// void visitBarrierBlock(SILBasicBlock *)
|
|
///
|
|
/// /// Invoked with either the function entry block or one of the specified
|
|
/// /// initial blocks.
|
|
/// void visitInitialBlock(SILBasicBlock *)
|
|
/// }
|
|
template <typename Effects>
|
|
template <typename Visitor>
|
|
bool IterativeBackwardReachability<Effects>::findBarrier(SILInstruction *from,
|
|
SILBasicBlock *block,
|
|
Visitor &visitor) {
|
|
for (auto *instruction = from; instruction;
|
|
instruction = instruction->getPreviousInstruction()) {
|
|
auto effect = effects.effectForInstruction(instruction);
|
|
if (!effect)
|
|
continue;
|
|
if (effect == Effect::Gen()) {
|
|
continue;
|
|
}
|
|
// effect == Effect::Kill
|
|
visitor.visitBarrierInstruction(instruction);
|
|
return true;
|
|
}
|
|
if (block->hasPhi()) {
|
|
if (auto effect = effects.effectForPhi(block)) {
|
|
if (effect == Effect::Kill()) {
|
|
visitor.visitBarrierPhi(block);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
assert(result.getEffectForBlock(block) != Effect::Kill());
|
|
if (stopAtBlock(block)) {
|
|
visitor.visitInitialBlock(block);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: IterativeBackwardReachability::Result - State
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// The previously recorded end state of the specified block.
|
|
template <typename Effects>
|
|
typename IterativeBackwardReachability<Effects>::State
|
|
IterativeBackwardReachability<Effects>::Result::getEndStateForBlock(
|
|
SILBasicBlock *block) {
|
|
if (!discoveredBlocks.contains(block))
|
|
return State::Unreachable();
|
|
if (unreachableEndBlocks.contains(block))
|
|
return State::Unreachable();
|
|
if (unknownEndBlocks.contains(block))
|
|
return State::Unknown();
|
|
return State::Reachable();
|
|
}
|
|
|
|
/// Record a transition of the indicated block to the specified state.
|
|
///
|
|
/// States must transition down the State lattice.
|
|
///
|
|
/// The state is recorded in:
|
|
/// - unreachableEndBlocks; blocks contained here are ::Unreachable
|
|
/// - unknownEndBlocks; blocks contained here are ::Unknown
|
|
/// Blocks that are in neither are ::Reachable.
|
|
template <typename Effects>
|
|
bool IterativeBackwardReachability<Effects>::Result::setEndStateforBlock(
|
|
SILBasicBlock *block, State state) {
|
|
switch (state.value) {
|
|
case State::Value::Unreachable: {
|
|
auto previouslyUnknown = unknownEndBlocks.contains(block);
|
|
unknownEndBlocks.erase(block);
|
|
auto notPreviouslyUnreachable = unreachableEndBlocks.insert(block);
|
|
assert(!previouslyUnknown ||
|
|
notPreviouslyUnreachable &&
|
|
"block was previously in both unknown and unreachable?!");
|
|
return previouslyUnknown || notPreviouslyUnreachable;
|
|
}
|
|
case State::Value::Reachable: {
|
|
auto previouslyUnknown = unknownEndBlocks.contains(block);
|
|
unknownEndBlocks.erase(block);
|
|
assert(!unreachableEndBlocks.contains(block) &&
|
|
"transitioning up lattice?! unreachable->reachable");
|
|
return previouslyUnknown;
|
|
}
|
|
case State::Value::Unknown: {
|
|
auto notPreviouslyUnknown = unknownEndBlocks.insert(block);
|
|
assert(!unreachableEndBlocks.contains(block) &&
|
|
"transitioning up lattice?! unreachable->unknown");
|
|
assert(!notPreviouslyUnknown &&
|
|
"transitioning up lattice?! reachable->unknown");
|
|
return notPreviouslyUnknown;
|
|
}
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: IterativeBackwardReachability::Result - Effects
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// The summary effect of the specified block on reachability, as previously
|
|
/// recorded.
|
|
template <typename Effects>
|
|
typename IterativeBackwardReachability<Effects>::Effect
|
|
IterativeBackwardReachability<Effects>::Result::getEffectForBlock(
|
|
SILBasicBlock *block) {
|
|
if (genBlocks.contains(block))
|
|
return Effect::Gen();
|
|
if (killBlocks.contains(block))
|
|
return Effect::Kill();
|
|
return Effect::NoEffect();
|
|
}
|
|
|
|
/// Record the summary effect of the specified block.
|
|
///
|
|
/// If it is statically known that the block has not already had its effect set
|
|
/// to an incompatible value, we can avoid clearing the old effect. For
|
|
/// example, if this is the first time a block's state has been set, or if we
|
|
/// are setting a block's state to ::Gen and we already know it isn't ::Kill.
|
|
template <typename Effects>
|
|
template <bool SkipOverwrite>
|
|
void IterativeBackwardReachability<Effects>::Result::setEffectForBlock(
|
|
SILBasicBlock *block, Effect effect) {
|
|
switch (effect.value) {
|
|
case Effect::Value::NoEffect:
|
|
if (!SkipOverwrite) {
|
|
genBlocks.erase(block);
|
|
killBlocks.erase(block);
|
|
}
|
|
return;
|
|
case Effect::Value::Gen:
|
|
genBlocks.insert(block);
|
|
if (!SkipOverwrite)
|
|
killBlocks.erase(block);
|
|
return;
|
|
case Effect::Value::Kill:
|
|
killBlocks.insert(block);
|
|
if (!SkipOverwrite)
|
|
genBlocks.erase(block);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: findBarriersBackward
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
using llvm::ArrayRef;
|
|
using llvm::function_ref;
|
|
|
|
struct ReachableBarriers final {
|
|
/// Instructions which are barriers.
|
|
llvm::SmallVector<SILInstruction *, 4> instructions;
|
|
|
|
/// Blocks one of whose phis is a barrier.
|
|
llvm::SmallVector<SILBasicBlock *, 4> phis;
|
|
|
|
/// Boundary edges; edges such that
|
|
/// (1) the target block is reachable-at-begin
|
|
/// (2) at least one adjacent edge's target is not reachable-at-begin.
|
|
llvm::SmallVector<SILBasicBlock *, 4> edges;
|
|
|
|
/// Terminal blocks that were reached; blocks such that
|
|
/// (1) the block is reachable-at-begin
|
|
/// (2) it is either the function's entry block or one of the blocks passed
|
|
/// to findBarriersBackward's \p initialBlocks parameter.
|
|
llvm::SmallVector<SILBasicBlock *, 2> initialBlocks;
|
|
|
|
ReachableBarriers() {}
|
|
ReachableBarriers(ReachableBarriers const &) = delete;
|
|
ReachableBarriers &operator=(ReachableBarriers const &) = delete;
|
|
};
|
|
|
|
/// Walk backwards from the specified \p roots through at the earliest \p
|
|
/// initialBlocks to populate \p barriers by querying \p isBarrier along the
|
|
/// way.
|
|
///
|
|
/// If \p initialBlocks is empty, dataflow continues to the begin of the
|
|
/// function.
|
|
void findBarriersBackward(ArrayRef<SILInstruction *> roots,
|
|
ArrayRef<SILBasicBlock *> initialBlocks,
|
|
SILFunction &function, ReachableBarriers &barriers,
|
|
function_ref<bool(SILInstruction *)> isBarrier);
|
|
|
|
} // end namespace swift
|
|
|
|
#endif
|