Skip to content

MultiDefPrunedLiveness: add support for arbitrary uses/defs. #64926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 53 additions & 34 deletions include/swift/SIL/PrunedLiveness.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,6 @@ class PrunedLiveBlocks {
/// Optional vector of live blocks for clients that deterministically iterate.
SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr;

/// Only a clean bitfield can be initialized.
bool cleanFlag = true;

/// Once the first def has been initialized, uses can be added.
bool initializedFlag = false;

Expand All @@ -207,7 +204,6 @@ class PrunedLiveBlocks {

void invalidate() {
initializedFlag = false;
cleanFlag = false;
}

void initializeDiscoveredBlocks(
Expand All @@ -229,17 +225,21 @@ class PrunedLiveBlocks {
}

/// Update this liveness result for a single use.
IsLive updateForUse(SILInstruction *user) {
///
/// \p isUseBeforeDef is true if \p user occures before the first def in this
/// block. This indicates "liveness holes" inside the block, causing liveness
/// to propagate to predecessors.
IsLive updateForUse(SILInstruction *user, bool isUseBeforeDef) {
assert(isInitialized() && "at least one definition must be initialized");

auto *block = user->getParent();
auto liveness = getBlockLiveness(block);
// If a block is already marked live, assume that liveness was propagated to
// its predecessors. This assumes that uses will never be added above a def
// in the same block.
if (liveness != Dead)
return liveness;

if (!isUseBeforeDef) {
auto liveness = getBlockLiveness(block);
// If a block is already marked live, it must either "kill" liveness, or
// liveness was already propagated to its predecessors.
if (liveness != Dead)
return liveness;
}
computeUseBlockLiveness(block);
return getBlockLiveness(block);
}
Expand Down Expand Up @@ -348,6 +348,7 @@ struct LiveRangeSummary {
/// necessarily include liveness up to destroy_value or end_borrow
/// instructions.
class PrunedLiveness {
protected:
PrunedLiveBlocks liveBlocks;

// Map all "interesting" user instructions in this def's live range to a flag
Expand Down Expand Up @@ -390,27 +391,6 @@ class PrunedLiveness {
liveBlocks.initializeDefBlock(defBB);
}

/// For flexibility, \p lifetimeEnding is provided by the
/// caller. PrunedLiveness makes no assumptions about the def-use
/// relationships that generate liveness. For example, use->isLifetimeEnding()
/// cannot distinguish the end of the borrow scope that defines this extended
/// live range vs. a nested borrow scope within the extended live range.
void updateForUse(SILInstruction *user, bool lifetimeEnding);

/// Updates the liveness for a whole borrow scope, beginning at \p op.
/// Returns false if this cannot be done. This assumes that nested OSSA
/// lifetimes are complete.
InnerBorrowKind updateForBorrowingOperand(Operand *operand);

/// Update liveness for an interior pointer use. These are normally handled
/// like an instantaneous use. But if \p operand "borrows" a value for the
/// duration of a scoped address (store_borrow), then update liveness for the
/// entire scope. This assumes that nested OSSA lifetimes are complete.
AddressUseKind checkAndUpdateInteriorPointer(Operand *operand);

/// Update this liveness to extend across the given liveness.
void extendAcrossLiveness(PrunedLiveness &otherLiveness);

PrunedLiveBlocks::IsLive getBlockLiveness(SILBasicBlock *bb) const {
return liveBlocks.getBlockLiveness(bb);
}
Expand Down Expand Up @@ -565,6 +545,27 @@ class PrunedLiveRange : public PrunedLiveness {
SILValue value);

public:
/// For flexibility, \p lifetimeEnding is provided by the
/// caller. PrunedLiveness makes no assumptions about the def-use
/// relationships that generate liveness. For example, use->isLifetimeEnding()
/// cannot distinguish the end of the borrow scope that defines this extended
/// live range vs. a nested borrow scope within the extended live range.
void updateForUse(SILInstruction *user, bool lifetimeEnding);

/// Updates the liveness for a whole borrow scope, beginning at \p op.
/// Returns false if this cannot be done. This assumes that nested OSSA
/// lifetimes are complete.
InnerBorrowKind updateForBorrowingOperand(Operand *operand);

/// Update liveness for an interior pointer use. These are normally handled
/// like an instantaneous use. But if \p operand "borrows" a value for the
/// duration of a scoped address (store_borrow), then update liveness for the
/// entire scope. This assumes that nested OSSA lifetimes are complete.
AddressUseKind checkAndUpdateInteriorPointer(Operand *operand);

/// Update this liveness to extend across the given liveness.
void extendAcrossLiveness(PrunedLiveness &otherLiveness);

/// Update liveness for all direct uses of \p def. Transitively follows
/// guaranteed forwards up to but not including guaranteed phis. If \p def is
/// used by a guaranteed phi return InnerBorrowKind::Reborrowed.
Expand Down Expand Up @@ -660,6 +661,9 @@ class SSAPrunedLiveness : public PrunedLiveRange<SSAPrunedLiveness> {
return def->getParentBlock() == block;
}

/// In SSA, uses never occur before the single def.
bool isUserBeforeDef(SILInstruction *user) const { return false; }

/// SSA implementation of computeBoundary.
void findBoundariesInBlock(SILBasicBlock *block, bool isLiveOut,
PrunedLivenessBoundary &boundary) const;
Expand Down Expand Up @@ -711,7 +715,7 @@ class MultiDefPrunedLiveness : public PrunedLiveRange<MultiDefPrunedLiveness> {
}

void initializeDef(SILInstruction *defInst) {
initializeDefNode(defInst->asSILNode());
initializeDefNode(cast<SILNode>(defInst));
}

void initializeDef(SILArgument *defArg) { initializeDefNode(defArg); }
Expand All @@ -733,10 +737,25 @@ class MultiDefPrunedLiveness : public PrunedLiveRange<MultiDefPrunedLiveness> {
return defs.contains(cast<SILNode>(inst));
}

bool isDef(SILArgument *arg) const {
return defs.contains(arg);
}

bool isDefBlock(SILBasicBlock *block) const {
return defBlocks.contains(block);
}

/// Return true if \p user occurs before the first def in the same basic
/// block. In classical liveness dataflow terms, gen/kill conditions over all
/// users in 'bb' are:
///
/// Gen(bb) |= !isDefBlock(bb) || isUserBeforeDef(bb)
/// Kill(bb) &= isDefBlock(bb) && !isUserBeforeDef(bb)
///
/// If 'bb' has no users, it is neither a Gen nor Kill. Otherwise, Gen and
/// Kill are complements.
bool isUserBeforeDef(SILInstruction *user) const;

/// Multi-Def implementation of computeBoundary.
void findBoundariesInBlock(SILBasicBlock *block, bool isLiveOut,
PrunedLivenessBoundary &boundary) const;
Expand Down
2 changes: 1 addition & 1 deletion include/swift/SIL/ScopedAddressUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ struct ScopedAddressValue {
///
/// Valid for any type of liveness, SSA or MultiDef, that may be used by a
/// scoped address.
AddressUseKind updateTransitiveLiveness(PrunedLiveness &liveness) const;
AddressUseKind updateTransitiveLiveness(SSAPrunedLiveness &liveness) const;

/// Create appropriate scope ending instruction at \p insertPt.
void createScopeEnd(SILBasicBlock::iterator insertPt, SILLocation loc) const;
Expand Down
8 changes: 4 additions & 4 deletions lib/SIL/Utils/OSSALifetimeCompletion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
///
/// Interior liveness handles the following cases naturally:
///
/// When completing the lifetime if the initial value, %v1, transitively
/// include all dominated reborrows. %phi1 in this example:
/// When completing the lifetime of the initial value, %v1, transitively
/// include all uses of dominated reborrows as, such as %phi1 in this example:
///
/// %v1 = ...
/// cond_br bb1, bb2
Expand All @@ -31,8 +31,8 @@
/// end_borrow %phi1
/// %k1 = destroy_value %v1 // must be below end_borrow %phi1
///
/// When completing the lifetime for a (%phi2) transitively include all inner
/// adjacent reborrows (%phi1):
/// When completing the lifetime for a phi (%phi2) transitively include all
/// uses of inner adjacent reborrows, such as %phi1 in this example:
///
/// bb1:
/// %v1 = ...
Expand Down
Loading