From ca503b54b70f7b0fc3caf2b460c0f5615155bef4 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Fri, 16 Sep 2022 19:05:40 -0700 Subject: [PATCH 01/19] Redesign PrunedLiveness APIs, introducing live ranges First restore the basic PrunedLiveness abstraction to its original intention. Move code outside of the basic abstraction that polutes the abstraction and is fundamentally wrong from the perspective of the liveness abstraction. Most clients need to reason about live ranges, including the def points, not just liveness based on use points. Add a PrunedLiveRange layer of types that understand where the live range is defined. Knowing where the live range is defined (the kill set) helps reliably check that arbitrary points are within the boundary. This way, the client doesn't need to be manage this on its own. We can also support holes in the live range for non-SSA liveness. This makes it safe and correct for the way liveness is now being used. This layer safety handles: - multiple defs - instructions that are both uses and defs - dead values - unreachable code - self-loops So it's no longer the client's responsibility to check these things! Add SSAPrunedLiveness and MultiDefPrunedLiveness to safely handle each situation. Split code that I can't figure out into DiagnosticPrunedLiveness. Hopefully it will be deleted soon. --- include/swift/SIL/OwnershipUtils.h | 3 +- include/swift/SIL/PrunedLiveness.h | 420 ++++++++++++----- include/swift/SIL/ScopedAddressUtils.h | 12 +- .../Utils/CanonicalOSSALifetime.h | 10 +- .../Utils/CanonicalizeBorrowScope.h | 6 +- .../SILOptimizer/Utils/OwnershipOptUtils.h | 4 +- lib/SIL/Utils/OwnershipUtils.cpp | 11 +- lib/SIL/Utils/PrunedLiveness.cpp | 441 +++++++++++------- lib/SIL/Utils/ScopedAddressUtils.cpp | 12 +- .../LoadBorrowImmutabilityChecker.cpp | 4 +- lib/SIL/Verifier/SILVerifier.cpp | 4 +- .../Mandatory/AddressLowering.cpp | 5 +- .../Mandatory/DiagnoseLifetimeIssues.cpp | 6 +- .../MoveKillsCopyableAddressesChecker.cpp | 8 +- .../MoveKillsCopyableValuesChecker.cpp | 7 +- .../Utils/CanonicalOSSALifetime.cpp | 32 +- lib/SILOptimizer/Utils/GenericCloner.cpp | 2 +- .../Utils/LexicalDestroyFolding.cpp | 2 +- lib/SILOptimizer/Utils/OwnershipOptUtils.cpp | 15 +- 19 files changed, 648 insertions(+), 356 deletions(-) diff --git a/include/swift/SIL/OwnershipUtils.h b/include/swift/SIL/OwnershipUtils.h index ce320448c66db..8ecb258fa0955 100644 --- a/include/swift/SIL/OwnershipUtils.h +++ b/include/swift/SIL/OwnershipUtils.h @@ -16,6 +16,7 @@ #include "swift/Basic/Debug.h" #include "swift/Basic/LLVM.h" #include "swift/SIL/MemAccessUtils.h" +#include "swift/SIL/PrunedLiveness.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILBasicBlock.h" #include "swift/SIL/SILInstruction.h" @@ -567,7 +568,7 @@ struct BorrowedValue { bool isLocalScope() const { return kind.isLocalScope(); } /// Add this scopes live blocks into the PrunedLiveness result. - void computeLiveness(PrunedLiveness &liveness) const; + void computeLiveness(SSAPrunedLiveness &liveness) const; /// Returns true if \p uses are completely within this borrow introducer's /// local scope. diff --git a/include/swift/SIL/PrunedLiveness.h b/include/swift/SIL/PrunedLiveness.h index 140fdf48af0ec..a2ce056c93578 100644 --- a/include/swift/SIL/PrunedLiveness.h +++ b/include/swift/SIL/PrunedLiveness.h @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2022 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 @@ -12,9 +12,7 @@ /// /// Incrementally compute and represent basic block liveness of a single live /// range. The live range is defined by points in the CFG, independent of any -/// particular SSA value; however, it must be contiguous. Unlike traditional -/// variable liveness, a definition within the live range does not create a -/// "hole" in the live range. The client initializes liveness with a set of +/// particular SSA value. The client initializes liveness with a set of /// definition blocks, typically a single block. The client then incrementally /// updates liveness by providing a set of "interesting" uses one at a time. /// @@ -85,16 +83,14 @@ /// | Use | [LiveWithin] /// ----- /// -/// -/// An invariant is that for any liveness region, the post-dominating blocks of -/// the region are the LiveWithin regions. -/// //===----------------------------------------------------------------------===// #ifndef SWIFT_SILOPTIMIZER_UTILS_PRUNEDLIVENESS_H #define SWIFT_SILOPTIMIZER_UTILS_PRUNEDLIVENESS_H #include "swift/AST/TypeExpansionContext.h" +#include "swift/SIL/BasicBlockDatastructures.h" +#include "swift/SIL/NodeDatastructures.h" #include "swift/SIL/SILBasicBlock.h" #include "swift/SIL/SILFunction.h" #include "llvm/ADT/MapVector.h" @@ -109,15 +105,14 @@ class DeadEndBlocks; /// liveness by first initializing "def" blocks, then incrementally feeding uses /// to updateForUse(). /// -/// For SSA live ranges, a single "def" block will dominate all uses. If no def -/// block is provided, liveness is computed as if defined by a function -/// argument. If the client does not provide a single, dominating def block, -/// then the client must at least ensure that no uses precede the first -/// definition in a def block. Since this analysis does not remember the -/// positions of defs, it assumes that, within a block, uses follow -/// defs. Breaking this assumption will result in a "hole" in the live range in -/// which the def block's predecessors incorrectly remain dead. This situation -/// could be handled by adding an updateForUseBeforeFirstDef() API. +/// Incrementally building liveness is important for algorithms that create an +/// initial live region, perform some analysis on that, then expand the live +/// region by adding new uses before continuing the analysis. +/// +/// Initializing "def blocks" restricts liveness on any path through those def +/// blocks to the blocks that occur on or after the def block. If any uses is +/// not dominated by a def block, then liveness will include the entry block, +/// as if defined by a function argument /// /// We allow for multiple bits of liveness information to be tracked by /// internally using a SmallBitVector. The multiple bit tracking is useful when @@ -127,8 +122,7 @@ class DeadEndBlocks; /// represent dead by not having liveness state for a block. With multiple bits /// possible this is no longer true. /// -/// TODO: This can be made space-efficient if all clients can maintain a block -/// numbering so liveness info can be represented as bitsets across the blocks. +/// TODO: For efficiency, use BasicBlockBitfield rather than SmallDenseMap. class PrunedLiveBlocks { public: /// Per-block liveness state computed during backward dataflow propagation. @@ -173,6 +167,8 @@ class PrunedLiveBlocks { unsigned size() const { return bits.size() / 2; } + // FIXME: specialize this for scalar liveness, which is the critical path + // for all OSSA utilities. IsLive getLiveness(unsigned bitNo) const { SmallVector foundLiveness; getLiveness(bitNo, bitNo + 1, foundLiveness); @@ -282,6 +278,8 @@ class PrunedLiveBlocks { return isLive[0]; } + // FIXME: This API should directly return the live bitset. The live bitset + // type should have an api for querying and iterating over the live fields. void getBlockLiveness(SILBasicBlock *bb, unsigned startBitNo, unsigned endBitNo, SmallVectorImpl &foundLivenessInfo) const { @@ -297,6 +295,10 @@ class PrunedLiveBlocks { liveBlockIter->second.getLiveness(startBitNo, endBitNo, foundLivenessInfo); } + llvm::StringRef getStringRef(IsLive isLive) const; + void print(llvm::raw_ostream &OS) const; + void dump() const; + protected: void markBlockLive(SILBasicBlock *bb, unsigned bitNo, IsLive isLive) { markBlockLive(bb, bitNo, bitNo + 1, isLive); @@ -343,6 +345,11 @@ class PrunedLiveBlocks { /// boundary, then it is up to the client to figure out how to "extend" the /// lifetime beyond those uses. /// +/// Note: a live-out block may contain a lifetime-ending use. This happens when +/// the client is computing "extended" livenes, for example by ignoring +/// copies. Lifetime ending uses are irrelevant for finding the liveness +/// boundary. +/// /// Note: unlike OwnershipLiveRange, this represents a lifetime in terms of the /// CFG boundary rather that the use set, and, because it is "pruned", it only /// includes liveness generated by select uses. For example, it does not @@ -362,26 +369,9 @@ class PrunedLiveness { // Non-lifetime-ending within a LiveOut block are uninteresting. llvm::SmallMapVector users; - /// A side array that stores any non lifetime ending uses we find in live out - /// blocks. This is used to enable our callers to emit errors on non-lifetime - /// ending uses that extend liveness into a loop body. - SmallSetVector *nonLifetimeEndingUsesInLiveOut; - -private: - bool isWithinBoundaryHelper(SILInstruction *inst, SILValue def) const; - - bool areUsesWithinBoundaryHelper(ArrayRef uses, SILValue def, - DeadEndBlocks *deadEndBlocks) const; - - bool areUsesOutsideBoundaryHelper(ArrayRef uses, SILValue def, - DeadEndBlocks *deadEndBlocks) const; - public: - PrunedLiveness(SmallVectorImpl *discoveredBlocks = nullptr, - SmallSetVector - *nonLifetimeEndingUsesInLiveOut = nullptr) - : liveBlocks(1 /*num bits*/, discoveredBlocks), - nonLifetimeEndingUsesInLiveOut(nonLifetimeEndingUsesInLiveOut) {} + PrunedLiveness(SmallVectorImpl *discoveredBlocks = nullptr) + : liveBlocks(1 /*num bits*/, discoveredBlocks) {} bool empty() const { assert(!liveBlocks.empty() || users.empty()); @@ -391,8 +381,6 @@ class PrunedLiveness { void clear() { liveBlocks.clear(); users.clear(); - if (nonLifetimeEndingUsesInLiveOut) - nonLifetimeEndingUsesInLiveOut->clear(); } unsigned numLiveBlocks() const { return liveBlocks.numLiveBlocks(); } @@ -403,47 +391,6 @@ class PrunedLiveness { return liveBlocks.getDiscoveredBlocks(); } - using NonLifetimeEndingUsesInLiveOutRange = - iterator_range; - - NonLifetimeEndingUsesInLiveOutRange - getNonLifetimeEndingUsesInLiveOut() const { - assert(nonLifetimeEndingUsesInLiveOut && - "Called without passing in nonLifetimeEndingUsesInLiveOut to " - "constructor?!"); - return llvm::make_range(nonLifetimeEndingUsesInLiveOut->begin(), - nonLifetimeEndingUsesInLiveOut->end()); - } - - using NonLifetimeEndingUsesInLiveOutBlocksRange = - TransformRange>; - NonLifetimeEndingUsesInLiveOutBlocksRange - getNonLifetimeEndingUsesInLiveOutBlocks() const { - function_ref op; - op = [](const SILInstruction *&ptr) -> SILBasicBlock * { - return ptr->getParent(); - }; - return NonLifetimeEndingUsesInLiveOutBlocksRange( - getNonLifetimeEndingUsesInLiveOut(), op); - } - - using UserRange = iterator_range *>; - UserRange getAllUsers() const { - return llvm::make_range(users.begin(), users.end()); - } - - using UserBlockRange = TransformRange< - UserRange, - function_ref &)>>; - UserBlockRange getAllUserBlocks() const { - function_ref &)> op; - op = [](const std::pair &pair) -> SILBasicBlock * { - return pair.first->getParent(); - }; - return UserBlockRange(getAllUsers(), op); - } - void initializeDefBlock(SILBasicBlock *defBB) { liveBlocks.initializeDefBlock(defBB, 0); } @@ -466,7 +413,11 @@ class PrunedLiveness { return liveBlocks.getBlockLiveness(bb, 0); } - enum IsInterestingUser { NonUser, NonLifetimeEndingUse, LifetimeEndingUse }; + enum IsInterestingUser { + NonUser = 0, + NonLifetimeEndingUse, + LifetimeEndingUse + }; /// Return a result indicating whether the given user was identified as an /// interesting use of the current def and whether it ends the lifetime. @@ -477,50 +428,30 @@ class PrunedLiveness { return useIter->second ? LifetimeEndingUse : NonLifetimeEndingUse; } - /// Return true if \p inst occurs before the liveness boundary. Used when the - /// client already knows that inst occurs after the start of liveness. - bool isWithinBoundary(SILInstruction *inst) const; - - /// \p deadEndBlocks is optional. - bool areUsesWithinBoundary(ArrayRef uses, - DeadEndBlocks *deadEndBlocks) const; - - /// \p deadEndBlocks is optional. - bool areUsesOutsideBoundary(ArrayRef uses, - DeadEndBlocks *deadEndBlocks) const; - - /// PrunedLiveness utilities can be used with multiple defs. This api can be - /// used to check if \p inst occurs in between the definition \p def and the - /// liveness boundary. - // This api varies from isWithinBoundary(SILInstruction *inst) which cannot - // distinguish when \p inst is a use before definition in the same block as - // the definition. - bool isWithinBoundaryOfDef(SILInstruction *inst, SILValue def) const; - - /// Returns true when all \p uses are between \p def and the liveness boundary - /// \p deadEndBlocks is optional. - bool areUsesWithinBoundaryOfDef(ArrayRef uses, SILValue def, - DeadEndBlocks *deadEndBlocks) const; - - /// Returns true if any of the \p uses are before the \p def or after the - /// liveness boundary - /// \p deadEndBlocks is optional. - bool areUsesOutsideBoundaryOfDef(ArrayRef uses, SILValue def, - DeadEndBlocks *deadEndBlocks) const; - - /// Compute liveness for a single SSA definition. - void computeSSALiveness(SILValue def); + void print(llvm::raw_ostream &OS) const; + void dump() const; }; /// Record the last use points and CFG edges that form the boundary of /// PrunedLiveness. +/// +/// Dead defs may occur even when the liveness result has uses for every +/// definition because those uses may occur in unreachable blocks. A dead def +/// must either be a SILInstruction or SILArgument. This supports memory +/// location liveness, so there isn't necessary a defining SILValue. +/// +/// Each boundary edge is identified by its target block. The source of the edge +/// is the target block's single predecessor which must have at least one other +/// non-boundary successor. struct PrunedLivenessBoundary { SmallVector lastUsers; SmallVector boundaryEdges; + SmallVector deadDefs; void clear() { lastUsers.clear(); boundaryEdges.clear(); + deadDefs.clear(); } /// Visit the point at which a lifetime-ending instruction must be inserted, @@ -531,6 +462,55 @@ struct PrunedLivenessBoundary { llvm::function_ref visitor, DeadEndBlocks *deBlocks = nullptr); + void print(llvm::raw_ostream &OS) const; + void dump() const; +}; + +/// PrunedLiveness with information about defs for computing the live range +/// boundary. +/// +/// LivenessWithDefs implements: +/// +/// bool isInitialized() const +/// +/// bool isDef(SILInstruction *inst) const +/// +/// bool isDefBlock(SILBasicBlock *block) const +/// +/// SILArgument *getArgDef(SILBasicBlock *block) const +/// +/// SILInstruction *findPreviousDef(SILInstruction *searchPos, +/// SILInstruction *nextDef) +/// +template +class PrunedLiveRange : public PrunedLiveness { +protected: + const LivenessWithDefs &asImpl() const { + return static_cast(*this); + } + + PrunedLiveRange(SmallVectorImpl *discoveredBlocks = nullptr) + : PrunedLiveness(discoveredBlocks) {} + +public: + /// Update liveness for all direct uses of \p def. + void updateForDef(SILValue def); + + /// Check if \p inst occurs in between the definition this def and the + /// liveness boundary. + bool isWithinBoundary(SILInstruction *inst) const; + + /// Returns true when all \p uses are between this def and the liveness + /// boundary \p deadEndBlocks is optional. + bool areUsesWithinBoundary(ArrayRef uses, + DeadEndBlocks *deadEndBlocks) const; + + /// Returns true if any of the \p uses are before this def or after the + /// liveness boundary + /// \p deadEndBlocks is optional. + bool areUsesOutsideBoundary(ArrayRef uses, + DeadEndBlocks *deadEndBlocks) const; + /// Compute the boundary from the blocks discovered during liveness analysis. /// /// Precondition: \p liveness.getDiscoveredBlocks() is a valid list of all @@ -539,7 +519,7 @@ struct PrunedLivenessBoundary { /// The computed boundary will completely post-dominate, including dead end /// paths. The client should query DeadEndBlocks to ignore those dead end /// paths. - void compute(const PrunedLiveness &liveness); + void computeBoundary(PrunedLivenessBoundary &boundary) const; /// Compute the boundary from a backward CFG traversal from a known set of /// jointly post-dominating blocks. Avoids the need to record an ordered list @@ -552,8 +532,212 @@ struct PrunedLivenessBoundary { /// resulting partial boundary will have holes along those paths. The dead end /// successors of blocks in this live set on are not necessarily identified /// by DeadEndBlocks. - void compute(const PrunedLiveness &liveness, - ArrayRef postDomBlocks); + void computeBoundary(PrunedLivenessBoundary &boundary, + ArrayRef postDomBlocks) const; + +protected: + void findBoundariesInBlock(SILBasicBlock *block, bool isLiveOut, + PrunedLivenessBoundary &boundary) const; +}; + +// Singly-defined liveness. +// +// An SSA def results in pruned liveness with a contiguous liverange. +// +// An unreachable self-loop might result in a "gap" between the last use above +// the def in the same block. +// +// For SSA live ranges, a single "def" block dominates all uses. If no def +// block is provided, liveness is computed as if defined by a function +// argument. If the client does not provide a single, dominating def block, +// then the client must at least ensure that no uses precede the first +// definition in a def block. Since this analysis does not remember the +// positions of defs, it assumes that, within a block, uses follow +// defs. Breaking this assumption will result in a "hole" in the live range in +// which the def block's predecessors incorrectly remain dead. This situation +// could be handled by adding an updateForUseBeforeFirstDef() API. +class SSAPrunedLiveness : public PrunedLiveRange { + SILValue def; + SILInstruction *defInst = nullptr; // nullptr for argument defs. + +public: + SSAPrunedLiveness( + SmallVectorImpl *discoveredBlocks = nullptr) + : PrunedLiveRange(discoveredBlocks) {} + + SILValue getDef() const { return def; } + + void clear() { + def = SILValue(); + defInst = nullptr; + PrunedLiveRange::clear(); + } + + void initializeDef(SILValue def) { + assert(!this->def && "reinitialization"); + + this->def = def; + defInst = def->getDefiningInstruction(); + initializeDefBlock(def->getParentBlock()); + } + + bool isInitialized() const { return bool(def); } + + bool isDef(SILInstruction *inst) const { return inst == defInst; } + + bool isDefBlock(SILBasicBlock *block) const { + return def->getParentBlock() == block; + } + + /// If the argument list of \p block contains a definition, return it. + SILArgument *getArgDef(SILBasicBlock *block) const { + if (auto *arg = dyn_cast(def)) { + if (arg->getParent() == block) + return arg; + } + return nullptr; + } + + /// Return the definition if it occurs in the same block before \p searchPos. + /// + /// Precondition: if the definition occurs in the same block on or after \p + /// searchPos, then \p nextDef must point to the definition. + SILInstruction *findPreviousDef(SILInstruction *searchPos, + SILInstruction *nextDef) const { + if (!defInst || nextDef) { + assert(nextDef == defInst); + return nullptr; + } + auto *block = searchPos->getParent(); + return defInst->getParent() == block ? defInst : nullptr; + } + + /// Compute liveness for a single SSA definition. The lifetime-ending uses are + /// also recorded--destroy_value or end_borrow. However destroy_values might + /// not jointly-post dominate if dead-end blocks are present. + void compute() { + assert(def && "SSA def uninitialized"); + updateForDef(def); + } +}; + +/// MultiDefPrunedLiveness is computed incrementally by calling updateForUse. +/// +/// Defs should be initialized before calling updatingForUse on any def +/// that reaches the use. +class MultiDefPrunedLiveness : public PrunedLiveRange { + NodeSetVector defs; + BasicBlockSet defBlocks; + +public: + MultiDefPrunedLiveness( + SILFunction *function, + SmallVectorImpl *discoveredBlocks = nullptr) + : PrunedLiveRange(discoveredBlocks), defs(function), defBlocks(function) { + } + + void clear() { + llvm_unreachable("multi-def liveness cannot be reused"); + } + + void initializeDef(SILNode *def) { + assert(isa(def) || isa(def)); + defs.insert(def); + auto *block = def->getParentBlock(); + defBlocks.insert(block); + initializeDefBlock(block); + } + + bool isInitialized() const { return !defs.empty(); } + + bool isDef(SILInstruction *inst) const { + return defs.contains(cast(inst)); + } + + bool isDefBlock(SILBasicBlock *block) const { + return defBlocks.contains(block); + } + + /// If the argument list of \p block contains a definition, return it. + SILArgument *getArgDef(SILBasicBlock *block) const { + if (!isDefBlock(block)) + return nullptr; + + for (SILArgument *arg : block->getArguments()) { + if (defs.contains(arg)) + return arg; + } + return nullptr; + } + + /// Return the previous definition that occurs in the same block before \p + /// searchPos, or nullptr if none exists. + /// + /// Precondition: if a definition occurs in the same block on or after \p + /// searchPos, then \p nextDef must point to the next definition. searchPos + /// cannot point to nextDef. + SILInstruction *findPreviousDef(SILInstruction *searchPos, + SILInstruction *nextDef) const; + + /// Compute liveness for a all currently initialized definitions. The + /// lifetime-ending uses are also recorded--destroy_value or + /// end_borrow. However destroy_values might not jointly-post dominate if + /// dead-end blocks are present. + void compute(); +}; + +//===----------------------------------------------------------------------===// +// DiagnosticPrunedLiveness +//===----------------------------------------------------------------------===// + +// FIXME: it isn't clear what this is for or what nonLifetimeEndingUseInLiveOut +// means precisely. +class DiagnosticPrunedLiveness : public SSAPrunedLiveness { + /// A side array that stores any non lifetime ending uses we find in live out + /// blocks. This is used to enable our callers to emit errors on non-lifetime + /// ending uses that extend liveness into a loop body. + SmallSetVector *nonLifetimeEndingUsesInLiveOut; + +public: + DiagnosticPrunedLiveness( + SmallVectorImpl *discoveredBlocks = nullptr, + SmallSetVector *nonLifetimeEndingUsesInLiveOut = + nullptr) + : SSAPrunedLiveness(discoveredBlocks), + nonLifetimeEndingUsesInLiveOut(nonLifetimeEndingUsesInLiveOut) {} + + void clear() { + SSAPrunedLiveness::clear(); + if (nonLifetimeEndingUsesInLiveOut) + nonLifetimeEndingUsesInLiveOut->clear(); + } + + void updateForUse(SILInstruction *user, bool lifetimeEnding); + + using NonLifetimeEndingUsesInLiveOutRange = + iterator_range; + + NonLifetimeEndingUsesInLiveOutRange + getNonLifetimeEndingUsesInLiveOut() const { + assert(nonLifetimeEndingUsesInLiveOut && + "Called without passing in nonLifetimeEndingUsesInLiveOut to " + "constructor?!"); + return llvm::make_range(nonLifetimeEndingUsesInLiveOut->begin(), + nonLifetimeEndingUsesInLiveOut->end()); + } + + using NonLifetimeEndingUsesInLiveOutBlocksRange = + TransformRange>; + NonLifetimeEndingUsesInLiveOutBlocksRange + getNonLifetimeEndingUsesInLiveOutBlocks() const { + function_ref op; + op = [](const SILInstruction *&ptr) -> SILBasicBlock * { + return ptr->getParent(); + }; + return NonLifetimeEndingUsesInLiveOutBlocksRange( + getNonLifetimeEndingUsesInLiveOut(), op); + } }; //===----------------------------------------------------------------------===// diff --git a/include/swift/SIL/ScopedAddressUtils.h b/include/swift/SIL/ScopedAddressUtils.h index 7e5155e6ea3fb..2dae6e35ac5f7 100644 --- a/include/swift/SIL/ScopedAddressUtils.h +++ b/include/swift/SIL/ScopedAddressUtils.h @@ -83,20 +83,20 @@ struct ScopedAddressValue { SILValue operator*() { return value; } SILValue operator*() const { return value; } - /// Returns true if \p op is a scope edning use of the scoped address value. + /// Returns true if \p op is a scope ending use of the scoped address value. bool isScopeEndingUse(Operand *op) const; /// Pass all scope ending instructions to the visitor. bool visitScopeEndingUses(function_ref visitor) const; /// Returns false, if liveness cannot be computed due to pointer escape or - /// unkown address use. Add this scope's live blocks into the PrunedLiveness - /// result. - bool computeLiveness(PrunedLiveness &liveness) const; + /// unkown address use. Add this scope's live blocks into the SSA + /// PrunedLiveness result. + bool computeLiveness(SSAPrunedLiveness &liveness) const; /// Create appropriate scope ending instruction at \p insertPt. void createScopeEnd(SILBasicBlock::iterator insertPt, SILLocation loc) const; /// Create scope ending instructions at \p liveness boundary. - void endScopeAtLivenessBoundary(PrunedLiveness *liveness) const; + void endScopeAtLivenessBoundary(SSAPrunedLiveness *liveness) const; }; llvm::raw_ostream &operator<<(llvm::raw_ostream &os, @@ -105,7 +105,7 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &os, /// Returns true if there are other store_borrows enclosed within a store_borrow /// \p sbi's scope bool hasOtherStoreBorrowsInLifetime(StoreBorrowInst *sbi, - PrunedLiveness *liveness, + SSAPrunedLiveness *liveness, DeadEndBlocks *deadEndBlocks); /// Extend the store_borrow \p sbi's scope such that it encloses \p newUsers. diff --git a/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h b/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h index cdbbbd8c05190..e020de3b4eecc 100644 --- a/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h +++ b/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h @@ -267,9 +267,6 @@ class CanonicalizeOSSALifetime { InstructionDeleter &deleter; - /// Current copied def for which this state describes the liveness. - SILValue currentDef; - /// Original points in the CFG where the current value's lifetime is consumed /// or destroyed. For guaranteed values it remains empty. A backward walk from /// these blocks must discover all uses on paths that lead to a return or @@ -293,7 +290,7 @@ class CanonicalizeOSSALifetime { /// Pruned liveness for the extended live range including copies. For this /// purpose, only consuming instructions are considered "lifetime /// ending". end_borrows do not end a liverange that may include owned copies. - PrunedLiveness liveness; + SSAPrunedLiveness liveness; /// The destroys of the value. These are not uses, but need to be recorded so /// that we know when the last use in a consuming block is (without having to @@ -355,7 +352,7 @@ class CanonicalizeOSSALifetime { accessBlockAnalysis(accessBlockAnalysis), domTree(domTree), deleter(deleter) {} - SILValue getCurrentDef() const { return currentDef; } + SILValue getCurrentDef() const { return liveness.getDef(); } void initDef(SILValue def) { assert(consumingBlocks.empty() && debugValues.empty() && liveness.empty()); @@ -364,8 +361,7 @@ class CanonicalizeOSSALifetime { accessBlocks = nullptr; consumes.clear(); - currentDef = def; - liveness.initializeDefBlock(def->getParentBlock()); + liveness.initializeDef(def); } void clearLiveness() { diff --git a/include/swift/SILOptimizer/Utils/CanonicalizeBorrowScope.h b/include/swift/SILOptimizer/Utils/CanonicalizeBorrowScope.h index 98e8085a3da06..65f4d0fda6183 100644 --- a/include/swift/SILOptimizer/Utils/CanonicalizeBorrowScope.h +++ b/include/swift/SILOptimizer/Utils/CanonicalizeBorrowScope.h @@ -61,7 +61,7 @@ class CanonicalizeBorrowScope { /// Pruned liveness for the extended live range including copies. For this /// purpose, only consuming instructions are considered "lifetime /// ending". end_borrows do not end a liverange that may include owned copies. - PrunedLiveness liveness; + SSAPrunedLiveness liveness; InstructionDeleter &deleter; @@ -87,7 +87,7 @@ class CanonicalizeBorrowScope { BorrowedValue getBorrowedValue() const { return borrowedValue; } - const PrunedLiveness &getLiveness() const { return liveness; } + const SSAPrunedLiveness &getLiveness() const { return liveness; } InstructionDeleter &getDeleter() { return deleter; } @@ -137,7 +137,7 @@ class CanonicalizeBorrowScope { updatedCopies.clear(); borrowedValue = borrow; - liveness.initializeDefBlock(borrowedValue->getParentBlock()); + liveness.initializeDef(borrowedValue.value); } bool computeBorrowLiveness(); diff --git a/include/swift/SILOptimizer/Utils/OwnershipOptUtils.h b/include/swift/SILOptimizer/Utils/OwnershipOptUtils.h index a60177a1ef5d3..f495106a73e81 100644 --- a/include/swift/SILOptimizer/Utils/OwnershipOptUtils.h +++ b/include/swift/SILOptimizer/Utils/OwnershipOptUtils.h @@ -100,8 +100,8 @@ class GuaranteedOwnershipExtension { DeadEndBlocks &deBlocks; // --- analysis state - PrunedLiveness guaranteedLiveness; - PrunedLiveness ownedLifetime; + SSAPrunedLiveness guaranteedLiveness; + SSAPrunedLiveness ownedLifetime; SmallVector ownedConsumeBlocks; BeginBorrowInst *beginBorrow = nullptr; diff --git a/lib/SIL/Utils/OwnershipUtils.cpp b/lib/SIL/Utils/OwnershipUtils.cpp index 200e1cb3467ed..bd37fbadc17be 100644 --- a/lib/SIL/Utils/OwnershipUtils.cpp +++ b/lib/SIL/Utils/OwnershipUtils.cpp @@ -753,8 +753,8 @@ llvm::raw_ostream &swift::operator<<(llvm::raw_ostream &os, } /// Add this scopes live blocks into the PrunedLiveness result. -void BorrowedValue::computeLiveness(PrunedLiveness &liveness) const { - liveness.initializeDefBlock(value->getParentBlock()); +void BorrowedValue::computeLiveness(SSAPrunedLiveness &liveness) const { + liveness.initializeDef(value); visitTransitiveLifetimeEndingUses([&](Operand *endOp) { if (endOp->getOperandOwnership() == OperandOwnership::EndBorrow) { liveness.updateForUse(endOp->getUser(), /*lifetimeEnding*/ true); @@ -779,7 +779,7 @@ bool BorrowedValue::areUsesWithinTransitiveScope( return true; // Compute the local scope's liveness. - PrunedLiveness liveness; + SSAPrunedLiveness liveness; computeLiveness(liveness); return liveness.areUsesWithinBoundary(uses, deadEndBlocks); } @@ -1085,8 +1085,9 @@ bool AddressOwnership::areUsesWithinLifetime( // --- A reference no borrow scope. Currently happens for project_box. // Compute the reference value's liveness. - PrunedLiveness liveness; - liveness.computeSSALiveness(root); + SSAPrunedLiveness liveness; + liveness.initializeDef(root); + liveness.compute(); return liveness.areUsesWithinBoundary(uses, &deadEndBlocks); } diff --git a/lib/SIL/Utils/PrunedLiveness.cpp b/lib/SIL/Utils/PrunedLiveness.cpp index 56ff201600f0a..fefb01d664b86 100644 --- a/lib/SIL/Utils/PrunedLiveness.cpp +++ b/lib/SIL/Utils/PrunedLiveness.cpp @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2022 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 @@ -92,19 +92,9 @@ void PrunedLiveBlocks::updateForUse( //===----------------------------------------------------------------------===// void PrunedLiveness::updateForUse(SILInstruction *user, bool lifetimeEnding) { - auto useBlockLive = liveBlocks.updateForUse(user, 0); - // Record all uses of blocks on the liveness boundary. For blocks marked - // LiveWithin, the boundary is considered to be the last use in the block. - // - // FIXME: Why is nonLifetimeEndingUsesInLiveOut inside PrunedLiveness, and - // what does it mean? Blocks may transition to LiveOut later. Or they may - // already be LiveOut from a previous use. After computing liveness, clients - // should check uses that are in PrunedLivenessBoundary. - if (!lifetimeEnding && useBlockLive == PrunedLiveBlocks::LiveOut) { - if (nonLifetimeEndingUsesInLiveOut) - nonLifetimeEndingUsesInLiveOut->insert(user); - return; - } + assert(!empty() && "at least one definition must be initialized"); + + liveBlocks.updateForUse(user, 0); // Note that a user may use the current value from multiple operands. If any // of the uses are non-lifetime-ending, then we must consider the user // itself non-lifetime-ending; it cannot be a final destroy point because @@ -149,143 +139,71 @@ void PrunedLiveness::extendAcrossLiveness(PrunedLiveness &otherLivesness) { } } -bool PrunedLiveness::isWithinBoundaryHelper(SILInstruction *inst, - SILValue def) const { - SILBasicBlock *block = inst->getParent(); - - /// Returns true if \p inst is before \p def in this block. - auto foundInBlockBeforeDef = [](SILInstruction *inst, SILBasicBlock *block, - SILValue def) { - if (!def || def->getParentBlock() != block) { - return false; - } - auto *defInst = def->getDefiningInstruction(); - if (!defInst) { - return false; - } - // Check if instruction is before the definition - for (SILInstruction &it : - make_range(block->begin(), defInst->getIterator())) { - if (&it == inst) { - return true; - } - } - return false; - }; - - switch (getBlockLiveness(block)) { - case PrunedLiveBlocks::Dead: - return false; - case PrunedLiveBlocks::LiveOut: - return !foundInBlockBeforeDef(inst, block, def); - case PrunedLiveBlocks::LiveWithin: - if (foundInBlockBeforeDef(inst, block, def)) { - return false; - } - // The boundary is within this block. This instruction is before the - // boundary iff any interesting uses occur after it. - for (SILInstruction &it : - make_range(std::next(inst->getIterator()), block->end())) { - switch (isInterestingUser(&it)) { - case PrunedLiveness::NonUser: - break; - case PrunedLiveness::NonLifetimeEndingUse: - case PrunedLiveness::LifetimeEndingUse: - return true; - } - } - return false; +llvm::StringRef PrunedLiveBlocks::getStringRef(IsLive isLive) const { + switch (isLive) { + case Dead: + return "Dead"; + case LiveWithin: + return "LiveWithin"; + case LiveOut: + return "LiveOut"; } } -bool PrunedLiveness::isWithinBoundary(SILInstruction *inst) const { - return isWithinBoundaryHelper(inst, /*def*/ SILValue()); -} - -bool PrunedLiveness::isWithinBoundaryOfDef(SILInstruction *inst, - SILValue def) const { - return isWithinBoundaryHelper(inst, def); -} - -bool PrunedLiveness::areUsesWithinBoundaryHelper( - ArrayRef uses, SILValue def, - DeadEndBlocks *deadEndBlocks) const { - auto checkDeadEnd = [deadEndBlocks](SILInstruction *inst) { - return deadEndBlocks && deadEndBlocks->isDeadEnd(inst->getParent()); - }; - - for (auto *use : uses) { - auto *user = use->getUser(); - if (!isWithinBoundaryHelper(user, def) && !checkDeadEnd(user)) - return false; +void PrunedLiveBlocks::print(llvm::raw_ostream &OS) const { + if (!discoveredBlocks) { + OS << "No deterministic live block list\n"; + } + for (auto *block : *discoveredBlocks) { + block->printAsOperand(OS); + OS + << ": " << getStringRef(this->getBlockLiveness(block, 0)) << "\n"; } - return true; -} - -bool PrunedLiveness::areUsesWithinBoundary(ArrayRef uses, - DeadEndBlocks *deadEndBlocks) const { - return areUsesWithinBoundaryHelper(uses, SILValue(), deadEndBlocks); } -bool PrunedLiveness::areUsesWithinBoundaryOfDef( - ArrayRef uses, SILValue def, - DeadEndBlocks *deadEndBlocks) const { - return areUsesWithinBoundaryHelper(uses, def, deadEndBlocks); +void PrunedLiveBlocks::dump() const { + print(llvm::dbgs()); } -bool PrunedLiveness::areUsesOutsideBoundaryHelper( - ArrayRef uses, SILValue def, - DeadEndBlocks *deadEndBlocks) const { - auto checkDeadEnd = [deadEndBlocks](SILInstruction *inst) { - return deadEndBlocks && deadEndBlocks->isDeadEnd(inst->getParent()); - }; - - for (auto *use : uses) { - auto *user = use->getUser(); - if (isWithinBoundaryHelper(user, def) || checkDeadEnd(user)) - return false; +void PrunedLiveness::print(llvm::raw_ostream &OS) const { + liveBlocks.print(OS); + for (auto &userAndIsLifetimeEnding : users) { + if (userAndIsLifetimeEnding.second) + OS << "lifetime-ending user: "; + else + OS << "regular user: "; + userAndIsLifetimeEnding.first->print(OS); } - return true; } -bool PrunedLiveness::areUsesOutsideBoundary( - ArrayRef uses, DeadEndBlocks *deadEndBlocks) const { - return areUsesOutsideBoundaryHelper(uses, SILValue(), deadEndBlocks); +void PrunedLiveness::dump() const { + print(llvm::dbgs()); } -bool PrunedLiveness::areUsesOutsideBoundaryOfDef( - ArrayRef uses, SILValue def, - DeadEndBlocks *deadEndBlocks) const { - return areUsesOutsideBoundaryHelper(uses, def, deadEndBlocks); -} +//===----------------------------------------------------------------------===// +// PrunedLivenessBoundary +//===----------------------------------------------------------------------===// -// An SSA def meets all the criteria for pruned liveness--def dominates all -// uses with no holes in the liverange. The lifetime-ending uses are also -// recorded--destroy_value or end_borrow. However destroy_values may not -// jointly-post dominate if dead-end blocks are present. -// -// Note: Uses with OperandOwnership::NonUse cannot be considered normal uses for -// liveness. Otherwise, liveness would need to separately track non-uses -// everywhere. Non-uses cannot be treated like normal non-lifetime-ending uses -// because they can occur on both applies, which need to extend liveness to the -// return point, and on forwarding instructions, like init_existential_ref, -// which need to consume their use even when type-dependent operands exist. -void PrunedLiveness::computeSSALiveness(SILValue def) { - initializeDefBlock(def->getParentBlock()); - for (Operand *use : def->getUses()) { - switch (use->getOperandOwnership()) { - default: - updateForUse(use->getUser(), use->isLifetimeEnding()); - break; - case OperandOwnership::NonUse: - break; - case OperandOwnership::Borrow: - updateForBorrowingOperand(use); - break; +void PrunedLivenessBoundary::print(llvm::raw_ostream &OS) const { + for (auto *user : lastUsers) { + OS << "last user: " << *user; + } + for (auto *block : boundaryEdges) { + OS << "boundary edge: "; + block->printAsOperand(OS); + OS << "\n"; + } + if (!deadDefs.empty()) { + for (auto *deadDef : deadDefs) { + OS << "dead def: " << *deadDef; } } } +void PrunedLivenessBoundary::dump() const { + print(llvm::dbgs()); +} + void PrunedLivenessBoundary::visitInsertionPoints( llvm::function_ref visitor, DeadEndBlocks *deBlocks) { @@ -309,40 +227,161 @@ void PrunedLivenessBoundary::visitInsertionPoints( visitor(edge->begin()); } + for (SILNode *deadDef : deadDefs) { + if (auto *arg = dyn_cast(deadDef)) + visitor(arg->getParent()->begin()); + else + visitor(std::next(cast(deadDef)->getIterator())); + } } -// Use \p liveness to find the last use in \p bb and add it to \p -// boundary.lastUsers. -static void findLastUserInBlock(SILBasicBlock *bb, - PrunedLivenessBoundary &boundary, - const PrunedLiveness &liveness) { - for (auto instIter = bb->rbegin(), endIter = bb->rend(); instIter != endIter; - ++instIter) { - auto *inst = &*instIter; - if (liveness.isInterestingUser(inst) == PrunedLiveness::NonUser) - continue; +//===----------------------------------------------------------------------===// +// PrunedLiveRange +//===----------------------------------------------------------------------===// - boundary.lastUsers.push_back(inst); - return; +template +void PrunedLiveRange::updateForDef(SILValue def) { + // Note: Uses with OperandOwnership::NonUse cannot be considered normal uses + // for liveness. Otherwise, liveness would need to separately track non-uses + // everywhere. Non-uses cannot be treated like normal non-lifetime-ending uses + // because they can occur on both applies, which need to extend liveness to + // the return point, and on forwarding instructions, like + // init_existential_ref, which need to consume their use even when + // type-dependent operands exist. + for (Operand *use : def->getUses()) { + switch (use->getOperandOwnership()) { + default: + updateForUse(use->getUser(), use->isLifetimeEnding()); + break; + case OperandOwnership::NonUse: + break; + case OperandOwnership::Borrow: + updateForBorrowingOperand(use); + break; + } } - llvm_unreachable("No user in LiveWithin block"); } -void PrunedLivenessBoundary::compute(const PrunedLiveness &liveness) { - for (SILBasicBlock *bb : liveness.getDiscoveredBlocks()) { +template +bool PrunedLiveRange::isWithinBoundary( + SILInstruction *inst) const { + assert(asImpl().isInitialized()); + + auto *block = inst->getParent(); + auto blockLiveness = getBlockLiveness(block); + if (blockLiveness == PrunedLiveBlocks::Dead) + return false; + + bool isLive = blockLiveness == PrunedLiveBlocks::LiveOut; + + if (isLive && !asImpl().isDefBlock(block)) + return true; + + // Check if instruction is between a last use and a definition + for (SILInstruction &it : llvm::reverse(*block)) { + // the def itself is not within the boundary, so cancel liveness before + // matching 'inst'. + if (asImpl().isDef(inst)) { + isLive = false; + } + if (&it == inst) { + return isLive; + } + if (!isLive && isInterestingUser(&it)) { + isLive = true; + } + } + llvm_unreachable("instruction must be in its parent block"); +} + +template +bool PrunedLiveRange::areUsesWithinBoundary( + ArrayRef uses, DeadEndBlocks *deadEndBlocks) const { + assert(asImpl().isInitialized()); + + auto checkDeadEnd = [deadEndBlocks](SILInstruction *inst) { + return deadEndBlocks && deadEndBlocks->isDeadEnd(inst->getParent()); + }; + for (auto *use : uses) { + auto *user = use->getUser(); + if (!asImpl().isWithinBoundary(user) && !checkDeadEnd(user)) + return false; + } + return true; +} + +template +bool PrunedLiveRange::areUsesOutsideBoundary( + ArrayRef uses, DeadEndBlocks *deadEndBlocks) const { + assert(asImpl().isInitialized()); + + auto checkDeadEnd = [deadEndBlocks](SILInstruction *inst) { + return deadEndBlocks && deadEndBlocks->isDeadEnd(inst->getParent()); + }; + for (auto *use : uses) { + auto *user = use->getUser(); + if (asImpl().isWithinBoundary(user) || checkDeadEnd(user)) + return false; + } + return true; +} + +template +void PrunedLiveRange::findBoundariesInBlock( + SILBasicBlock *block, bool isLiveOut, + PrunedLivenessBoundary &boundary) const { + assert(asImpl().isInitialized()); + + bool isLive = isLiveOut; + bool isDefBlock = asImpl().isDefBlock(block); + SILInstruction *nextDef = nullptr; + SILInstruction *searchPos = block->getTerminator(); + while (searchPos) { + if (isLive) { + nextDef = asImpl().findPreviousDef(searchPos, nextDef); + if (!nextDef) + return; + + searchPos = nextDef; + isLive = false; + } else if (isDefBlock) { + // Check if the previous instruction is a def before checking whether it + // is a use. The same instruction can be both a dead def and boundary use. + if (asImpl().isDef(searchPos)) { + boundary.deadDefs.push_back(cast(searchPos)); + } + } + if (isInterestingUser(searchPos)) { + boundary.lastUsers.push_back(searchPos); + isLive = true; + } + searchPos = searchPos->getPreviousInstruction(); + } + if (!isLive && isDefBlock) { + if (SILArgument *deadArg = asImpl().getArgDef(block)) { + boundary.deadDefs.push_back(deadArg); + } + } +} + +template +void PrunedLiveRange::computeBoundary( + PrunedLivenessBoundary &boundary) const { + assert(asImpl().isInitialized()); + + for (SILBasicBlock *block : getDiscoveredBlocks()) { // Process each block that has not been visited and is not LiveOut. - switch (liveness.getBlockLiveness(bb)) { + switch (getBlockLiveness(block)) { case PrunedLiveBlocks::LiveOut: - for (SILBasicBlock *succBB : bb->getSuccessors()) { - if (liveness.getBlockLiveness(succBB) == PrunedLiveBlocks::Dead) { - boundaryEdges.push_back(succBB); + for (SILBasicBlock *succBB : block->getSuccessors()) { + if (getBlockLiveness(succBB) == PrunedLiveBlocks::Dead) { + boundary.boundaryEdges.push_back(succBB); } } + findBoundariesInBlock(block, /*isLiveOut*/ true, boundary); break; case PrunedLiveBlocks::LiveWithin: { - // The liveness boundary is inside this block. Insert a final destroy - // inside the block if it doesn't already have one. - findLastUserInBlock(bb, *this, liveness); + findBoundariesInBlock(block, /*isLiveOut*/ false, boundary); break; } case PrunedLiveBlocks::Dead: @@ -351,8 +390,12 @@ void PrunedLivenessBoundary::compute(const PrunedLiveness &liveness) { } } -void PrunedLivenessBoundary::compute(const PrunedLiveness &liveness, - ArrayRef postDomBlocks) { +template +void PrunedLiveRange::computeBoundary( + PrunedLivenessBoundary &boundary, + ArrayRef postDomBlocks) const { + assert(asImpl().isInitialized()); + if (postDomBlocks.empty()) return; // all paths must be dead-ends or infinite loops @@ -360,27 +403,24 @@ void PrunedLivenessBoundary::compute(const PrunedLiveness &liveness, // Visit each post-dominating block as the starting point for a // backward CFG traversal. - for (auto *bb : postDomBlocks) { - blockWorklist.push(bb); + for (auto *block : postDomBlocks) { + blockWorklist.push(block); } - while (auto *bb = blockWorklist.pop()) { + while (auto *block = blockWorklist.pop()) { // Process each block that has not been visited and is not LiveOut. - switch (liveness.getBlockLiveness(bb)) { + switch (getBlockLiveness(block)) { case PrunedLiveBlocks::LiveOut: - // A lifetimeEndBlock may be determined to be LiveOut after analyzing - // the extended liveness. It is irrelevant for finding the boundary. + findBoundariesInBlock(block, /*isLiveOut*/ true, boundary); break; case PrunedLiveBlocks::LiveWithin: { - // The liveness boundary is inside this block. Insert a final destroy - // inside the block if it doesn't already have one. - findLastUserInBlock(bb, *this, liveness); + findBoundariesInBlock(block, /*isLiveOut*/ false, boundary); break; } case PrunedLiveBlocks::Dead: // Continue searching upward to find the pruned liveness boundary. - for (auto *predBB : bb->getPredecessorBlocks()) { - if (liveness.getBlockLiveness(predBB) == PrunedLiveBlocks::LiveOut) { - boundaryEdges.push_back(bb); + for (auto *predBB : block->getPredecessorBlocks()) { + if (getBlockLiveness(predBB) == PrunedLiveBlocks::LiveOut) { + boundary.boundaryEdges.push_back(block); } else { blockWorklist.pushIfNotVisited(predBB); } @@ -390,6 +430,69 @@ void PrunedLivenessBoundary::compute(const PrunedLiveness &liveness, } } +namespace swift { +template class PrunedLiveRange; +template class PrunedLiveRange; +} // namespace swift + +//===----------------------------------------------------------------------===// +// MultiDefPrunedLiveness +//===----------------------------------------------------------------------===// + +SILInstruction * +MultiDefPrunedLiveness::findPreviousDef(SILInstruction *searchPos, + SILInstruction *nextDef) const { + auto *block = searchPos->getParent(); + if (!defBlocks.contains(block)) + return nullptr; + + auto it = searchPos->getReverseIterator(); + if (searchPos == nextDef) { + ++it; + } + for (auto end = block->rend(); it != end; ++it) { + if (isDef(&*it)) + return &*it; + } + return nullptr; +} + +void MultiDefPrunedLiveness::compute() { + assert(isInitialized() && "defs uninitialized"); + + for (SILNode *defNode : defs) { + if (auto *arg = dyn_cast(defNode)) + updateForDef(arg); + else { + for (auto result : cast(defNode)->getResults()) { + updateForDef(result); + } + } + } +} + +//===----------------------------------------------------------------------===// +// DiagnosticPrunedLiveness +//===----------------------------------------------------------------------===// + +// FIXME: This is wrong. Why is nonLifetimeEndingUsesInLiveOut inside +// PrunedLiveness, and what does it mean? Blocks may transition to LiveOut +// later. Or they may already be LiveOut from a previous use. After computing +// liveness, clients should check uses that are in PrunedLivenessBoundary. +void DiagnosticPrunedLiveness:: +updateForUse(SILInstruction *user, bool lifetimeEnding) { + PrunedLiveness::updateForUse(user, 0); + + auto useBlockLive = getBlockLiveness(user->getParent()); + // Record all uses of blocks on the liveness boundary. For blocks marked + // LiveWithin, the boundary is considered to be the last use in the block. + if (!lifetimeEnding && useBlockLive == PrunedLiveBlocks::LiveOut) { + if (nonLifetimeEndingUsesInLiveOut) + nonLifetimeEndingUsesInLiveOut->insert(user); + return; + } +} + //===----------------------------------------------------------------------===// // Field Sensitive PrunedLiveness //===----------------------------------------------------------------------===// diff --git a/lib/SIL/Utils/ScopedAddressUtils.cpp b/lib/SIL/Utils/ScopedAddressUtils.cpp index 132668b831b2f..144d4ea19a2da 100644 --- a/lib/SIL/Utils/ScopedAddressUtils.cpp +++ b/lib/SIL/Utils/ScopedAddressUtils.cpp @@ -86,7 +86,7 @@ bool ScopedAddressValue::visitScopeEndingUses( } } -bool ScopedAddressValue::computeLiveness(PrunedLiveness &liveness) const { +bool ScopedAddressValue::computeLiveness(SSAPrunedLiveness &liveness) const { SmallVector uses; // Collect all uses that need to be enclosed by the scope. auto addressKind = findTransitiveUsesForAddress(value, &uses); @@ -123,7 +123,7 @@ void ScopedAddressValue::createScopeEnd(SILBasicBlock::iterator insertPt, } void ScopedAddressValue::endScopeAtLivenessBoundary( - PrunedLiveness *liveness) const { + SSAPrunedLiveness *liveness) const { // If no users exist, create scope ending instruction immediately after the // scoped address value. if (liveness->empty()) { @@ -133,7 +133,7 @@ void ScopedAddressValue::endScopeAtLivenessBoundary( } PrunedLivenessBoundary scopedAddressBoundary; - scopedAddressBoundary.compute(*liveness); + liveness->computeBoundary(scopedAddressBoundary); // Go over the boundary and create scope ending instructions. scopedAddressBoundary.visitInsertionPoints( [&](SILBasicBlock::iterator insertPt) { @@ -142,7 +142,7 @@ void ScopedAddressValue::endScopeAtLivenessBoundary( } bool swift::hasOtherStoreBorrowsInLifetime(StoreBorrowInst *storeBorrow, - PrunedLiveness *liveness, + SSAPrunedLiveness *liveness, DeadEndBlocks *deadEndBlocks) { SmallVector otherStoreBorrows; // Collect all other store_borrows to the destination of \p storeBorrow @@ -157,7 +157,7 @@ bool swift::hasOtherStoreBorrowsInLifetime(StoreBorrowInst *storeBorrow, for (auto *otherStoreBorrow : otherStoreBorrows) { // Return true, if otherStoreBorrow was in \p storeBorrow's scope - if (liveness->isWithinBoundaryOfDef(otherStoreBorrow, storeBorrow)) { + if (liveness->isWithinBoundary(otherStoreBorrow)) { return true; } } @@ -171,7 +171,7 @@ bool swift::extendStoreBorrow(StoreBorrowInst *sbi, ScopedAddressValue scopedAddress(sbi); SmallVector discoveredBlocks; - PrunedLiveness storeBorrowLiveness(&discoveredBlocks); + SSAPrunedLiveness storeBorrowLiveness(&discoveredBlocks); bool success = scopedAddress.computeLiveness(storeBorrowLiveness); // If all new uses are within store_borrow boundary, no need for extension. diff --git a/lib/SIL/Verifier/LoadBorrowImmutabilityChecker.cpp b/lib/SIL/Verifier/LoadBorrowImmutabilityChecker.cpp index 9680d3f55088a..882507bc8014a 100644 --- a/lib/SIL/Verifier/LoadBorrowImmutabilityChecker.cpp +++ b/lib/SIL/Verifier/LoadBorrowImmutabilityChecker.cpp @@ -329,7 +329,7 @@ bool LoadBorrowImmutabilityAnalysis::isImmutableInScope( : SILValue(); BorrowedValue borrowedValue(lbi); - PrunedLiveness borrowLiveness; + SSAPrunedLiveness borrowLiveness; borrowedValue.computeLiveness(borrowLiveness); // Then for each write... @@ -346,7 +346,7 @@ bool LoadBorrowImmutabilityAnalysis::isImmutableInScope( continue; } - if (borrowLiveness.isWithinBoundaryOfDef(write, lbi)) { + if (borrowLiveness.isWithinBoundary(write)) { llvm::errs() << "Write: " << *write; return false; } diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index 2fcbfd64c7815..b57d9c7d63676 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -2260,7 +2260,7 @@ class SILVerifier : public SILVerifierBase { } bool checkScopedAddressUses(ScopedAddressValue scopedAddress, - PrunedLiveness *scopedAddressLiveness, + SSAPrunedLiveness *scopedAddressLiveness, DeadEndBlocks *deadEndBlocks) { SmallVector uses; findTransitiveUsesForAddress(scopedAddress.value, &uses); @@ -2444,7 +2444,7 @@ class SILVerifier : public SILVerifierBase { require(isa(SI->getDest()), "store_borrow destination can only be an alloc_stack"); - PrunedLiveness scopedAddressLiveness; + SSAPrunedLiveness scopedAddressLiveness; ScopedAddressValue scopedAddress(SI); bool success = scopedAddress.computeLiveness(scopedAddressLiveness); diff --git a/lib/SILOptimizer/Mandatory/AddressLowering.cpp b/lib/SILOptimizer/Mandatory/AddressLowering.cpp index 457e81d1bf104..d85b202c970eb 100644 --- a/lib/SILOptimizer/Mandatory/AddressLowering.cpp +++ b/lib/SILOptimizer/Mandatory/AddressLowering.cpp @@ -2857,13 +2857,14 @@ void UseRewriter::emitEndBorrows(SILValue value) { findInnerTransitiveGuaranteedUses(value, &usePoints); SmallVector discoveredBlocks; - PrunedLiveness liveness(&discoveredBlocks); + SSAPrunedLiveness liveness(&discoveredBlocks); + liveness.initializeDef(value); for (auto *use : usePoints) { assert(!use->isLifetimeEnding()); liveness.updateForUse(use->getUser(), /*lifetimeEnding*/ false); } PrunedLivenessBoundary guaranteedBoundary; - guaranteedBoundary.compute(liveness); + liveness.computeBoundary(guaranteedBoundary); guaranteedBoundary.visitInsertionPoints( [&](SILBasicBlock::iterator insertPt) { pass.getBuilder(insertPt).createEndBorrow(pass.genLoc(), value); diff --git a/lib/SILOptimizer/Mandatory/DiagnoseLifetimeIssues.cpp b/lib/SILOptimizer/Mandatory/DiagnoseLifetimeIssues.cpp index 8e9b2cea6574c..e8377d7f0cf65 100644 --- a/lib/SILOptimizer/Mandatory/DiagnoseLifetimeIssues.cpp +++ b/lib/SILOptimizer/Mandatory/DiagnoseLifetimeIssues.cpp @@ -64,7 +64,7 @@ class DiagnoseLifetimeIssues { static constexpr int maxCallDepth = 8; /// The liveness of the object in question, computed in visitUses. - PrunedLiveness liveness; + SSAPrunedLiveness liveness; /// All weak stores of the object, which are found in visitUses. llvm::SmallVector weakStores; @@ -305,7 +305,7 @@ getArgumentState(ApplySite ai, Operand *applyOperand, int callDepth) { } /// Returns true if \p inst is outside the pruned \p liveness. -static bool isOutOfLifetime(SILInstruction *inst, PrunedLiveness &liveness) { +static bool isOutOfLifetime(SILInstruction *inst, SSAPrunedLiveness &liveness) { // Check if the lifetime of the stored object ends at the store_weak. // // A more sophisticated analysis would be to check if there are no @@ -325,7 +325,7 @@ void DiagnoseLifetimeIssues::reportDeadStore(SILInstruction *allocationInst) { weakStores.clear(); SILValue storedDef = cast(allocationInst); - liveness.initializeDefBlock(storedDef->getParentBlock()); + liveness.initializeDef(storedDef); // Compute the canonical lifetime of storedDef, like the copy-propagation pass // would do. diff --git a/lib/SILOptimizer/Mandatory/MoveKillsCopyableAddressesChecker.cpp b/lib/SILOptimizer/Mandatory/MoveKillsCopyableAddressesChecker.cpp index c9ad9b02fa110..5bcc76f21bc27 100644 --- a/lib/SILOptimizer/Mandatory/MoveKillsCopyableAddressesChecker.cpp +++ b/lib/SILOptimizer/Mandatory/MoveKillsCopyableAddressesChecker.cpp @@ -575,11 +575,12 @@ namespace { struct ClosureArgDataflowState { SmallVector livenessWorklist; SmallVector consumingWorklist; - PrunedLiveness livenessForConsumes; + MultiDefPrunedLiveness livenessForConsumes; UseState &useState; public: - ClosureArgDataflowState(UseState &useState) : useState(useState) {} + ClosureArgDataflowState(SILFunction *function, UseState &useState) + : livenessForConsumes(function), useState(useState) {} bool process( SILArgument *arg, ClosureOperandState &state, @@ -752,6 +753,7 @@ void ClosureArgDataflowState::classifyUses(BasicBlockSet &initBlocks, for (auto *user : useState.inits) { if (upwardScanForInit(user, useState)) { LLVM_DEBUG(llvm::dbgs() << " Found init block at: " << *user); + livenessForConsumes.initializeDef(cast(user)); initBlocks.insert(user->getParent()); } } @@ -1961,7 +1963,7 @@ struct MoveKillsCopyableAddressesChecker { : fn(fn), useState(), dataflowState(funcBuilder, useState, applySiteToPromotedArgIndices, closureConsumes), - closureUseState(), closureUseDataflowState(closureUseState), + closureUseState(), closureUseDataflowState(fn, closureUseState), funcBuilder(funcBuilder) {} void cloneDeferCalleeAndRewriteUses( diff --git a/lib/SILOptimizer/Mandatory/MoveKillsCopyableValuesChecker.cpp b/lib/SILOptimizer/Mandatory/MoveKillsCopyableValuesChecker.cpp index 8072ee6869eb4..84ea75b79feed 100644 --- a/lib/SILOptimizer/Mandatory/MoveKillsCopyableValuesChecker.cpp +++ b/lib/SILOptimizer/Mandatory/MoveKillsCopyableValuesChecker.cpp @@ -58,13 +58,16 @@ struct CheckerLivenessInfo { SmallSetVector consumingUse; SmallSetVector nonLifetimeEndingUsesInLiveOut; SmallVector interiorPointerTransitiveUses; - PrunedLiveness liveness; + DiagnosticPrunedLiveness liveness; CheckerLivenessInfo() : nonLifetimeEndingUsesInLiveOut(), liveness(nullptr, &nonLifetimeEndingUsesInLiveOut) {} - void initDef(SILValue def) { defUseWorklist.insert(def); } + void initDef(SILValue def) { + liveness.initializeDef(def); + defUseWorklist.insert(def); + } /// Compute the liveness for any value currently in the defUseWorklist. /// diff --git a/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp b/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp index 2805da8350639..d37df09afa4bd 100644 --- a/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp +++ b/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp @@ -107,7 +107,7 @@ void swift::copyLiveUse(Operand *use, InstModCallbacks &instModCallbacks) { //===----------------------------------------------------------------------===// bool CanonicalizeOSSALifetime::computeCanonicalLiveness() { - defUseWorklist.initialize(currentDef); + defUseWorklist.initialize(getCurrentDef()); while (SILValue value = defUseWorklist.pop()) { SILPhiArgument *arg; if ((arg = dyn_cast(value)) && arg->isPhi()) { @@ -186,7 +186,7 @@ bool CanonicalizeOSSALifetime::computeCanonicalLiveness() { defUseWorklist.insert(cast(user)); break; } - if (is_contained(user->getOperandValues(), currentDef)) { + if (is_contained(user->getOperandValues(), getCurrentDef())) { // An adjacent phi consumes the value being reborrowed. Although this // use doesn't end the lifetime, this user does. liveness.updateForUse(user, /*lifetimeEnding*/ true); @@ -336,7 +336,7 @@ void CanonicalizeOSSALifetime::extendLivenessThroughOverlappingAccess() { // // It must be populated first so that we can test membership during the loop // (see findLastConsume). - BasicBlockSetVector blocksToVisit(currentDef->getFunction()); + BasicBlockSetVector blocksToVisit(getCurrentDef()->getFunction()); for (auto *block : consumingBlocks) { blocksToVisit.insert(block); } @@ -440,12 +440,12 @@ void CanonicalizeOSSALifetime::findOrInsertDestroyOnCFGEdge( assert(succBB->getSinglePredecessorBlock() == predBB && "value is live-out on another predBB successor: critical edge?"); - auto *di = findDestroyOnCFGEdge(succBB, currentDef); + auto *di = findDestroyOnCFGEdge(succBB, getCurrentDef()); if (!di) { auto pos = succBB->begin(); SILBuilderWithScope builder(pos); auto loc = RegularLocation::getAutoGeneratedLocation(pos->getLoc()); - di = builder.createDestroyValue(loc, currentDef); + di = builder.createDestroyValue(loc, getCurrentDef()); getCallbacks().createdNewInst(di); } consumes.recordFinalConsume(di); @@ -490,7 +490,7 @@ static void insertDestroyAtInst(SILBasicBlock::iterator pos, // values should be applied to that block summary which can provide the input // to rewriteCopies. void CanonicalizeOSSALifetime::findOrInsertDestroyInBlock(SILBasicBlock *bb) { - auto *defInst = currentDef->getDefiningInstruction(); + auto *defInst = getCurrentDef()->getDefiningInstruction(); DestroyValueInst *existingDestroy = nullptr; auto instIter = bb->getTerminator()->getIterator(); while (true) { @@ -512,8 +512,8 @@ void CanonicalizeOSSALifetime::findOrInsertDestroyInBlock(SILBasicBlock *bb) { findOrInsertDestroyOnCFGEdge(bb, succ); } } else { - insertDestroyAtInst(std::next(instIter), existingDestroy, currentDef, - consumes, getCallbacks()); + insertDestroyAtInst(std::next(instIter), existingDestroy, + getCurrentDef(), consumes, getCallbacks()); } return; case PrunedLiveness::LifetimeEndingUse: @@ -531,22 +531,22 @@ void CanonicalizeOSSALifetime::findOrInsertDestroyInBlock(SILBasicBlock *bb) { if (auto *destroy = dyn_cast(inst)) { auto destroyDef = CanonicalizeOSSALifetime::getCanonicalCopiedDef( destroy->getOperand()); - if (destroyDef == currentDef) { + if (destroyDef == getCurrentDef()) { existingDestroy = destroy; } } } if (instIter == bb->begin()) { - assert(cast(currentDef)->getParent() == bb); - insertDestroyAtInst(instIter, existingDestroy, currentDef, consumes, - getCallbacks()); + assert(cast(getCurrentDef())->getParent() == bb); + insertDestroyAtInst(instIter, existingDestroy, getCurrentDef(), + consumes, getCallbacks()); return; } --instIter; // If the original def is reached, this is a dead live range. Insert a // destroy immediately after the def. if (&*instIter == defInst) { - insertDestroyAtInst(std::next(instIter), existingDestroy, currentDef, + insertDestroyAtInst(std::next(instIter), existingDestroy, getCurrentDef(), consumes, getCallbacks()); return; } @@ -605,7 +605,7 @@ void CanonicalizeOSSALifetime::findOrInsertDestroys() { /// copies and destroys for deletion. Insert new copies for interior uses that /// require ownership of the used operand. void CanonicalizeOSSALifetime::rewriteCopies() { - assert(currentDef->getOwnershipKind() == OwnershipKind::Owned); + assert(getCurrentDef()->getOwnershipKind() == OwnershipKind::Owned); SmallSetVector instsToDelete; defUseWorklist.clear(); @@ -656,8 +656,8 @@ void CanonicalizeOSSALifetime::rewriteCopies() { }; // Perform a def-use traversal, visiting each use operand. - for (auto useIter = currentDef->use_begin(), endIter = currentDef->use_end(); - useIter != endIter;) { + for (auto useIter = getCurrentDef()->use_begin(), + endIter = getCurrentDef()->use_end(); useIter != endIter;) { Operand *use = *useIter++; if (!visitUse(use)) { copyLiveUse(use, getCallbacks()); diff --git a/lib/SILOptimizer/Utils/GenericCloner.cpp b/lib/SILOptimizer/Utils/GenericCloner.cpp index 95cc271873536..9e049967bc0d6 100644 --- a/lib/SILOptimizer/Utils/GenericCloner.cpp +++ b/lib/SILOptimizer/Utils/GenericCloner.cpp @@ -250,7 +250,7 @@ void GenericCloner::postFixUp(SILFunction *f) { continue; } discoveredBlocks.clear(); - PrunedLiveness storeBorrowLiveness(&discoveredBlocks); + SSAPrunedLiveness storeBorrowLiveness(&discoveredBlocks); bool success = scopedAddress.computeLiveness(storeBorrowLiveness); if (success) { scopedAddress.endScopeAtLivenessBoundary(&storeBorrowLiveness); diff --git a/lib/SILOptimizer/Utils/LexicalDestroyFolding.cpp b/lib/SILOptimizer/Utils/LexicalDestroyFolding.cpp index 4e87828ec7af1..27be788745d8c 100644 --- a/lib/SILOptimizer/Utils/LexicalDestroyFolding.cpp +++ b/lib/SILOptimizer/Utils/LexicalDestroyFolding.cpp @@ -644,7 +644,7 @@ bool findBorroweeUsage(Context const &context, BorroweeUsage &usage) { bool borroweeHasUsesWithinBorrowScope(Context const &context, BorroweeUsage const &usage) { - PrunedLiveness liveness; + SSAPrunedLiveness liveness; context.borrowedValue.computeLiveness(liveness); DeadEndBlocks deadEndBlocks(context.function); return !liveness.areUsesOutsideBoundary(usage.uses, &deadEndBlocks); diff --git a/lib/SILOptimizer/Utils/OwnershipOptUtils.cpp b/lib/SILOptimizer/Utils/OwnershipOptUtils.cpp index 57187d55dc35d..5056fe1e5d3dd 100644 --- a/lib/SILOptimizer/Utils/OwnershipOptUtils.cpp +++ b/lib/SILOptimizer/Utils/OwnershipOptUtils.cpp @@ -140,12 +140,13 @@ bool swift::computeGuaranteedBoundary(SILValue value, bool noEscape = findInnerTransitiveGuaranteedUses(value, &usePoints); SmallVector discoveredBlocks; - PrunedLiveness liveness(&discoveredBlocks); + SSAPrunedLiveness liveness(&discoveredBlocks); + liveness.initializeDef(value); for (auto *use : usePoints) { assert(!use->isLifetimeEnding()); liveness.updateForUse(use->getUser(), /*lifetimeEnding*/ false); } - boundary.compute(liveness); + liveness.computeBoundary(boundary); return noEscape; } @@ -239,7 +240,7 @@ GuaranteedOwnershipExtension::checkLifetimeExtension( if (ownershipKind != OwnershipKind::Owned) return Invalid; - ownedLifetime.initializeDefBlock(ownedValue->getParentBlock()); + ownedLifetime.initializeDef(ownedValue); for (Operand *use : ownedValue->getUses()) { auto *user = use->getUser(); if (use->isConsuming()) { @@ -260,17 +261,17 @@ void GuaranteedOwnershipExtension::transform(Status status) { return; case ExtendBorrow: { PrunedLivenessBoundary guaranteedBoundary; - guaranteedBoundary.compute(guaranteedLiveness, ownedConsumeBlocks); + guaranteedLiveness.computeBoundary(guaranteedBoundary, ownedConsumeBlocks); extendLocalBorrow(beginBorrow, guaranteedBoundary, deleter); break; } case ExtendLifetime: { ownedLifetime.extendAcrossLiveness(guaranteedLiveness); PrunedLivenessBoundary ownedBoundary; - ownedBoundary.compute(ownedLifetime, ownedConsumeBlocks); + ownedLifetime.computeBoundary(ownedBoundary, ownedConsumeBlocks); extendOwnedLifetime(beginBorrow->getOperand(), ownedBoundary, deleter); PrunedLivenessBoundary guaranteedBoundary; - guaranteedBoundary.compute(guaranteedLiveness, ownedConsumeBlocks); + guaranteedLiveness.computeBoundary(guaranteedBoundary, ownedConsumeBlocks); extendLocalBorrow(beginBorrow, guaranteedBoundary, deleter); break; } @@ -437,7 +438,7 @@ bool swift::areUsesWithinLexicalValueLifetime(SILValue value, return true; if (auto borrowedValue = BorrowedValue(value)) { - PrunedLiveness liveness; + SSAPrunedLiveness liveness; auto *function = value->getFunction(); borrowedValue.computeLiveness(liveness); DeadEndBlocks deadEndBlocks(function); From 40e03ef782cca23a3230bb736afd768a2c859e28 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Sat, 24 Sep 2022 23:03:19 -0700 Subject: [PATCH 02/19] Update passes to use SSAPrunedLiveness or MultiDefPrunedLiveness --- include/swift/SIL/OwnershipUtils.h | 6 ++++-- .../SILOptimizer/Utils/OwnershipOptUtils.h | 7 ++++--- lib/SIL/Utils/OwnershipUtils.cpp | 11 ++++++----- lib/SIL/Utils/PrunedLiveness.cpp | 2 +- lib/SIL/Utils/ScopedAddressUtils.cpp | 5 +++-- .../Verifier/LoadBorrowImmutabilityChecker.cpp | 4 ++-- .../MoveKillsCopyableAddressesChecker.cpp | 18 +++++------------- .../Transforms/AccessEnforcementOpts.cpp | 3 ++- .../Utils/LexicalDestroyFolding.cpp | 4 ++-- lib/SILOptimizer/Utils/OwnershipOptUtils.cpp | 6 +++--- 10 files changed, 32 insertions(+), 34 deletions(-) diff --git a/include/swift/SIL/OwnershipUtils.h b/include/swift/SIL/OwnershipUtils.h index 8ecb258fa0955..2e104888611f2 100644 --- a/include/swift/SIL/OwnershipUtils.h +++ b/include/swift/SIL/OwnershipUtils.h @@ -567,8 +567,10 @@ struct BorrowedValue { bool isLocalScope() const { return kind.isLocalScope(); } - /// Add this scopes live blocks into the PrunedLiveness result. - void computeLiveness(SSAPrunedLiveness &liveness) const; + /// Add this scope's live blocks into the PrunedLiveness result. This + /// includes reborrow scopes that are reachable from this borrow scope but not + /// necessarilly dominated by the borrow scope. + void computeTransitiveLiveness(MultiDefPrunedLiveness &liveness) const; /// Returns true if \p uses are completely within this borrow introducer's /// local scope. diff --git a/include/swift/SILOptimizer/Utils/OwnershipOptUtils.h b/include/swift/SILOptimizer/Utils/OwnershipOptUtils.h index f495106a73e81..91d7aa050997c 100644 --- a/include/swift/SILOptimizer/Utils/OwnershipOptUtils.h +++ b/include/swift/SILOptimizer/Utils/OwnershipOptUtils.h @@ -100,15 +100,16 @@ class GuaranteedOwnershipExtension { DeadEndBlocks &deBlocks; // --- analysis state - SSAPrunedLiveness guaranteedLiveness; + MultiDefPrunedLiveness guaranteedLiveness; SSAPrunedLiveness ownedLifetime; SmallVector ownedConsumeBlocks; BeginBorrowInst *beginBorrow = nullptr; public: GuaranteedOwnershipExtension(InstructionDeleter &deleter, - DeadEndBlocks &deBlocks) - : deleter(deleter), deBlocks(deBlocks) {} + DeadEndBlocks &deBlocks, SILFunction *function) + : deleter(deleter), deBlocks(deBlocks), + guaranteedLiveness(function) {} void clear() { guaranteedLiveness.clear(); diff --git a/lib/SIL/Utils/OwnershipUtils.cpp b/lib/SIL/Utils/OwnershipUtils.cpp index bd37fbadc17be..a24da33893a25 100644 --- a/lib/SIL/Utils/OwnershipUtils.cpp +++ b/lib/SIL/Utils/OwnershipUtils.cpp @@ -753,7 +753,8 @@ llvm::raw_ostream &swift::operator<<(llvm::raw_ostream &os, } /// Add this scopes live blocks into the PrunedLiveness result. -void BorrowedValue::computeLiveness(SSAPrunedLiveness &liveness) const { +void BorrowedValue:: +computeTransitiveLiveness(MultiDefPrunedLiveness &liveness) const { liveness.initializeDef(value); visitTransitiveLifetimeEndingUses([&](Operand *endOp) { if (endOp->getOperandOwnership() == OperandOwnership::EndBorrow) { @@ -761,8 +762,8 @@ void BorrowedValue::computeLiveness(SSAPrunedLiveness &liveness) const { return true; } assert(endOp->getOperandOwnership() == OperandOwnership::Reborrow); - auto *succBlock = cast(endOp->getUser())->getDestBB(); - liveness.initializeDefBlock(succBlock); + PhiOperand phiOper(endOp); + liveness.initializeDef(phiOper.getValue()); liveness.updateForUse(endOp->getUser(), /*lifetimeEnding*/ false); return true; }); @@ -779,8 +780,8 @@ bool BorrowedValue::areUsesWithinTransitiveScope( return true; // Compute the local scope's liveness. - SSAPrunedLiveness liveness; - computeLiveness(liveness); + MultiDefPrunedLiveness liveness(value->getFunction()); + computeTransitiveLiveness(liveness); return liveness.areUsesWithinBoundary(uses, deadEndBlocks); } diff --git a/lib/SIL/Utils/PrunedLiveness.cpp b/lib/SIL/Utils/PrunedLiveness.cpp index fefb01d664b86..277573df08396 100644 --- a/lib/SIL/Utils/PrunedLiveness.cpp +++ b/lib/SIL/Utils/PrunedLiveness.cpp @@ -281,7 +281,7 @@ bool PrunedLiveRange::isWithinBoundary( for (SILInstruction &it : llvm::reverse(*block)) { // the def itself is not within the boundary, so cancel liveness before // matching 'inst'. - if (asImpl().isDef(inst)) { + if (asImpl().isDef(&it)) { isLive = false; } if (&it == inst) { diff --git a/lib/SIL/Utils/ScopedAddressUtils.cpp b/lib/SIL/Utils/ScopedAddressUtils.cpp index 144d4ea19a2da..f023d9cb4cdc3 100644 --- a/lib/SIL/Utils/ScopedAddressUtils.cpp +++ b/lib/SIL/Utils/ScopedAddressUtils.cpp @@ -94,7 +94,7 @@ bool ScopedAddressValue::computeLiveness(SSAPrunedLiveness &liveness) const { return false; } - liveness.initializeDefBlock(value->getParentBlock()); + liveness.initializeDef(value); for (auto *use : uses) { // Update all collected uses as non-lifetime ending. liveness.updateForUse(use->getUser(), /* lifetimeEnding */ false); @@ -193,7 +193,8 @@ bool swift::extendStoreBorrow(StoreBorrowInst *sbi, InstModCallbacks tempCallbacks = callbacks; InstructionDeleter deleter(std::move(tempCallbacks)); - GuaranteedOwnershipExtension borrowExtension(deleter, *deadEndBlocks); + GuaranteedOwnershipExtension borrowExtension(deleter, *deadEndBlocks, + sbi->getFunction()); auto status = borrowExtension.checkBorrowExtension( BorrowedValue(sbi->getSrc()), newUses); if (status == GuaranteedOwnershipExtension::Invalid) { diff --git a/lib/SIL/Verifier/LoadBorrowImmutabilityChecker.cpp b/lib/SIL/Verifier/LoadBorrowImmutabilityChecker.cpp index 882507bc8014a..97a59be2203de 100644 --- a/lib/SIL/Verifier/LoadBorrowImmutabilityChecker.cpp +++ b/lib/SIL/Verifier/LoadBorrowImmutabilityChecker.cpp @@ -329,8 +329,8 @@ bool LoadBorrowImmutabilityAnalysis::isImmutableInScope( : SILValue(); BorrowedValue borrowedValue(lbi); - SSAPrunedLiveness borrowLiveness; - borrowedValue.computeLiveness(borrowLiveness); + MultiDefPrunedLiveness borrowLiveness(lbi->getFunction()); + borrowedValue.computeTransitiveLiveness(borrowLiveness); // Then for each write... for (auto *op : *writes) { diff --git a/lib/SILOptimizer/Mandatory/MoveKillsCopyableAddressesChecker.cpp b/lib/SILOptimizer/Mandatory/MoveKillsCopyableAddressesChecker.cpp index 5bcc76f21bc27..1700c8fa9c9cd 100644 --- a/lib/SILOptimizer/Mandatory/MoveKillsCopyableAddressesChecker.cpp +++ b/lib/SILOptimizer/Mandatory/MoveKillsCopyableAddressesChecker.cpp @@ -586,10 +586,6 @@ struct ClosureArgDataflowState { SILArgument *arg, ClosureOperandState &state, SmallBlotSetVector &postDominatingConsumingUsers); - void clear() { - livenessForConsumes.clear(); - } - private: /// Perform our liveness dataflow. Returns true if we found any liveness uses /// at all. These we will need to error upon. @@ -799,8 +795,6 @@ void ClosureArgDataflowState::classifyUses(BasicBlockSet &initBlocks, bool ClosureArgDataflowState::process( SILArgument *address, ClosureOperandState &state, SmallBlotSetVector &postDominatingConsumingUsers) { - clear(); - SILFunction *fn = address->getFunction(); assert(fn); @@ -867,9 +861,9 @@ bool ClosureArgDataflowState::process( return false; } - SWIFT_DEFER { livenessForConsumes.clear(); }; - auto *frontBlock = &*fn->begin(); - livenessForConsumes.initializeDefBlock(frontBlock); + //!!! FIXME: Why? + // auto *frontBlock = &*fn->begin(); + // livenessForConsumes.initializeDefBlock(frontBlock); for (unsigned i : indices(livenessWorklist)) { if (auto *ptr = livenessWorklist[i]) { @@ -1952,7 +1946,6 @@ struct MoveKillsCopyableAddressesChecker { UseState useState; DataflowState dataflowState; UseState closureUseState; - ClosureArgDataflowState closureUseDataflowState; SILOptFunctionBuilder &funcBuilder; llvm::SmallMapVector applySiteToPromotedArgIndices; @@ -1963,8 +1956,7 @@ struct MoveKillsCopyableAddressesChecker { : fn(fn), useState(), dataflowState(funcBuilder, useState, applySiteToPromotedArgIndices, closureConsumes), - closureUseState(), closureUseDataflowState(fn, closureUseState), - funcBuilder(funcBuilder) {} + closureUseState(), funcBuilder(funcBuilder) {} void cloneDeferCalleeAndRewriteUses( SmallVectorImpl &temporaryStorage, @@ -2087,7 +2079,7 @@ bool MoveKillsCopyableAddressesChecker::performClosureDataflow( if (!visitAccessPathUses(visitor, accessPath, fn)) return false; - SWIFT_DEFER { closureUseDataflowState.clear(); }; + ClosureArgDataflowState closureUseDataflowState(fn, closureUseState); return closureUseDataflowState.process(address, calleeOperandState, closureConsumes); } diff --git a/lib/SILOptimizer/Transforms/AccessEnforcementOpts.cpp b/lib/SILOptimizer/Transforms/AccessEnforcementOpts.cpp index 084e3d6bd365d..d33be36e96e4e 100644 --- a/lib/SILOptimizer/Transforms/AccessEnforcementOpts.cpp +++ b/lib/SILOptimizer/Transforms/AccessEnforcementOpts.cpp @@ -1009,7 +1009,8 @@ static bool extendOwnership(BeginAccessInst *parentInst, BeginAccessInst *childInst, InstructionDeleter &deleter, DeadEndBlocks &deBlocks) { - GuaranteedOwnershipExtension extension(deleter, deBlocks); + GuaranteedOwnershipExtension extension(deleter, deBlocks, + parentInst->getFunction()); auto status = extension.checkAddressOwnership(parentInst, childInst); switch (status) { case GuaranteedOwnershipExtension::Invalid: diff --git a/lib/SILOptimizer/Utils/LexicalDestroyFolding.cpp b/lib/SILOptimizer/Utils/LexicalDestroyFolding.cpp index 27be788745d8c..e31e89365da32 100644 --- a/lib/SILOptimizer/Utils/LexicalDestroyFolding.cpp +++ b/lib/SILOptimizer/Utils/LexicalDestroyFolding.cpp @@ -644,8 +644,8 @@ bool findBorroweeUsage(Context const &context, BorroweeUsage &usage) { bool borroweeHasUsesWithinBorrowScope(Context const &context, BorroweeUsage const &usage) { - SSAPrunedLiveness liveness; - context.borrowedValue.computeLiveness(liveness); + MultiDefPrunedLiveness liveness(context.function); + context.borrowedValue.computeTransitiveLiveness(liveness); DeadEndBlocks deadEndBlocks(context.function); return !liveness.areUsesOutsideBoundary(usage.uses, &deadEndBlocks); } diff --git a/lib/SILOptimizer/Utils/OwnershipOptUtils.cpp b/lib/SILOptimizer/Utils/OwnershipOptUtils.cpp index 5056fe1e5d3dd..a4a257ec6d7b7 100644 --- a/lib/SILOptimizer/Utils/OwnershipOptUtils.cpp +++ b/lib/SILOptimizer/Utils/OwnershipOptUtils.cpp @@ -198,7 +198,7 @@ GuaranteedOwnershipExtension::checkBorrowExtension( return Valid; // arguments have whole-function ownership assert(guaranteedLiveness.empty()); - borrow.computeLiveness(guaranteedLiveness); + borrow.computeTransitiveLiveness(guaranteedLiveness); if (guaranteedLiveness.areUsesWithinBoundary(newUses, &deBlocks)) return Valid; // reuse the borrow scope as-is @@ -438,9 +438,9 @@ bool swift::areUsesWithinLexicalValueLifetime(SILValue value, return true; if (auto borrowedValue = BorrowedValue(value)) { - SSAPrunedLiveness liveness; auto *function = value->getFunction(); - borrowedValue.computeLiveness(liveness); + MultiDefPrunedLiveness liveness(function); + borrowedValue.computeTransitiveLiveness(liveness); DeadEndBlocks deadEndBlocks(function); return liveness.areUsesWithinBoundary(uses, &deadEndBlocks); } From 464e353028f9031cf8329311720f38393c68098b Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Sun, 25 Sep 2022 18:54:40 -0700 Subject: [PATCH 03/19] Fix ScopedAddressValue::computeLiveness. Return the AddressUseKind. Fixes a bug in extendStoreBorrow where it was looking at an uninitialized liveness result whenever a pointer escape was present. --- include/swift/SIL/ScopedAddressUtils.h | 9 +++++---- lib/SIL/Utils/ScopedAddressUtils.cpp | 13 ++++++------- lib/SIL/Verifier/SILVerifier.cpp | 4 +++- lib/SILOptimizer/Utils/GenericCloner.cpp | 4 ++-- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/include/swift/SIL/ScopedAddressUtils.h b/include/swift/SIL/ScopedAddressUtils.h index 2dae6e35ac5f7..ad9de9f87e75b 100644 --- a/include/swift/SIL/ScopedAddressUtils.h +++ b/include/swift/SIL/ScopedAddressUtils.h @@ -15,6 +15,7 @@ #include "swift/Basic/Debug.h" #include "swift/Basic/LLVM.h" +#include "swift/SIL/OwnershipUtils.h" #include "swift/SIL/PrunedLiveness.h" #include "swift/SIL/SILBasicBlock.h" #include "swift/SIL/SILInstruction.h" @@ -87,10 +88,10 @@ struct ScopedAddressValue { bool isScopeEndingUse(Operand *op) const; /// Pass all scope ending instructions to the visitor. bool visitScopeEndingUses(function_ref visitor) const; - /// Returns false, if liveness cannot be computed due to pointer escape or - /// unkown address use. Add this scope's live blocks into the SSA - /// PrunedLiveness result. - bool computeLiveness(SSAPrunedLiveness &liveness) const; + /// Optimistically computes liveness for all known uses, and adds this scope's + /// live blocks into the SSA PrunedLiveness result. Returns AddressUseKind + /// indicated whether a PointerEscape or Unknown use was encountered. + AddressUseKind computeLiveness(SSAPrunedLiveness &liveness) const; /// Create appropriate scope ending instruction at \p insertPt. void createScopeEnd(SILBasicBlock::iterator insertPt, SILLocation loc) const; diff --git a/lib/SIL/Utils/ScopedAddressUtils.cpp b/lib/SIL/Utils/ScopedAddressUtils.cpp index f023d9cb4cdc3..b1573054eef50 100644 --- a/lib/SIL/Utils/ScopedAddressUtils.cpp +++ b/lib/SIL/Utils/ScopedAddressUtils.cpp @@ -86,13 +86,11 @@ bool ScopedAddressValue::visitScopeEndingUses( } } -bool ScopedAddressValue::computeLiveness(SSAPrunedLiveness &liveness) const { +AddressUseKind ScopedAddressValue:: +computeLiveness(SSAPrunedLiveness &liveness) const { SmallVector uses; // Collect all uses that need to be enclosed by the scope. auto addressKind = findTransitiveUsesForAddress(value, &uses); - if (addressKind != AddressUseKind::NonEscaping) { - return false; - } liveness.initializeDef(value); for (auto *use : uses) { @@ -103,7 +101,7 @@ bool ScopedAddressValue::computeLiveness(SSAPrunedLiveness &liveness) const { liveness.updateForUse(endOp->getUser(), /* isLifetimeEnding */ true); return true; }); - return true; + return addressKind; } void ScopedAddressValue::createScopeEnd(SILBasicBlock::iterator insertPt, @@ -172,14 +170,15 @@ bool swift::extendStoreBorrow(StoreBorrowInst *sbi, SmallVector discoveredBlocks; SSAPrunedLiveness storeBorrowLiveness(&discoveredBlocks); - bool success = scopedAddress.computeLiveness(storeBorrowLiveness); + + AddressUseKind useKind = scopedAddress.computeLiveness(storeBorrowLiveness); // If all new uses are within store_borrow boundary, no need for extension. if (storeBorrowLiveness.areUsesWithinBoundary(newUses, deadEndBlocks)) { return true; } - if (!success) { + if (useKind != AddressUseKind::NonEscaping) { return false; } diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index b57d9c7d63676..856627df3a3da 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -2446,7 +2446,9 @@ class SILVerifier : public SILVerifierBase { SSAPrunedLiveness scopedAddressLiveness; ScopedAddressValue scopedAddress(SI); - bool success = scopedAddress.computeLiveness(scopedAddressLiveness); + AddressUseKind useKind = + scopedAddress.computeLiveness(scopedAddressLiveness); + bool success = useKind == AddressUseKind::NonEscaping; require(!success || checkScopedAddressUses( scopedAddress, &scopedAddressLiveness, &DEBlocks), diff --git a/lib/SILOptimizer/Utils/GenericCloner.cpp b/lib/SILOptimizer/Utils/GenericCloner.cpp index 9e049967bc0d6..3cda3b21c7138 100644 --- a/lib/SILOptimizer/Utils/GenericCloner.cpp +++ b/lib/SILOptimizer/Utils/GenericCloner.cpp @@ -251,8 +251,8 @@ void GenericCloner::postFixUp(SILFunction *f) { } discoveredBlocks.clear(); SSAPrunedLiveness storeBorrowLiveness(&discoveredBlocks); - bool success = scopedAddress.computeLiveness(storeBorrowLiveness); - if (success) { + AddressUseKind useKind = scopedAddress.computeLiveness(storeBorrowLiveness); + if (useKind == AddressUseKind::NonEscaping) { scopedAddress.endScopeAtLivenessBoundary(&storeBorrowLiveness); continue; } From 4ef7f8590f577a224610d11b6935e6d8c7e1d9e6 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 26 Sep 2022 18:10:17 -0700 Subject: [PATCH 04/19] Fix findTransitiveUsesForAddress to handle incomplete scopes This fixes ScopedAddressValue::computeLiveness in unreachable code scenarios. For example: %storeBorrow = store_borrow %_ to %adr %loadBorrow = load_borrow %storeBorrow apply %f(%loadBorrow) : $@convention(thin) (...) -> Never Now recursively process uses of load_borrow as if they are address uses. Ultimately, this would be more efficiently handled by a recursive lifetime completion utility which would fixup the load_borrow scope before computing the store_borrow liveness. Fixes rdar://99874173: unreachable assert "No user in LiveWithin block" --- lib/SIL/Utils/OwnershipUtils.cpp | 16 +++++++++++----- lib/SIL/Utils/ScopedAddressUtils.cpp | 13 +++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/SIL/Utils/OwnershipUtils.cpp b/lib/SIL/Utils/OwnershipUtils.cpp index a24da33893a25..c57b981f695fd 100644 --- a/lib/SIL/Utils/OwnershipUtils.cpp +++ b/lib/SIL/Utils/OwnershipUtils.cpp @@ -923,6 +923,10 @@ bool BorrowedValue::visitInteriorPointerOperandHelper( return true; } +// FIXME: This does not yet assume complete lifetimes. Therefore, it currently +// recursively looks through scoped uses, such as load_borrow. We should +// separate the logic for lifetime completion from the logic that can assume +// complete lifetimes. AddressUseKind swift::findTransitiveUsesForAddress(SILValue projectedAddress, SmallVectorImpl *foundUses, @@ -1019,10 +1023,12 @@ swift::findTransitiveUsesForAddress(SILValue projectedAddress, // If we have a load_borrow, add it's end scope to the liveness requirement. if (auto *lbi = dyn_cast(user)) { if (foundUses) { - for (Operand *use : lbi->getUses()) { - if (use->endsLocalBorrowScope()) { - leafUse(use); - } + // FIXME: if we can assume complete lifetimes, then this should be + // as simple as: + // for (Operand *use : lbi->getUses()) { + // if (use->endsLocalBorrowScope()) { + if (!findInnerTransitiveGuaranteedUses(lbi, foundUses)) { + result = meet(result, AddressUseKind::PointerEscape); } } continue; @@ -1064,7 +1070,7 @@ swift::findTransitiveUsesForAddress(SILValue projectedAddress, if (onError) { (*onError)(op); } - result = AddressUseKind::Unknown; + result = meet(result, AddressUseKind::Unknown); } return result; } diff --git a/lib/SIL/Utils/ScopedAddressUtils.cpp b/lib/SIL/Utils/ScopedAddressUtils.cpp index b1573054eef50..119da176c1ec5 100644 --- a/lib/SIL/Utils/ScopedAddressUtils.cpp +++ b/lib/SIL/Utils/ScopedAddressUtils.cpp @@ -86,6 +86,19 @@ bool ScopedAddressValue::visitScopeEndingUses( } } +// Note: This is used to fixup an incomplete address scope, so cannot assume the +// scope's lifetime is already complete. Therefore, it needs to transitively +// process all address uses. +// +// FIXME: this should use the standard recursive lifetime completion +// utility. Otherwise dealing with nested incomplete lifetimes becomes expensive +// and complex. e.g. +// +// %storeBorrow = store_borrow %_ to %adr +// %loadBorrow = load_borrow %storeBorrow +// apply %f(%loadBorrow) : $@convention(thin) (...) -> Never +// unreachable +// AddressUseKind ScopedAddressValue:: computeLiveness(SSAPrunedLiveness &liveness) const { SmallVector uses; From da4f1b9a705575e44b47244854ed94a2c4a92b7b Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Thu, 29 Sep 2022 11:13:09 -0700 Subject: [PATCH 05/19] Add InnerBorrowKind and AddressUseKind to PrunedLiveness API The API for computing simple liveness now returns a SimpleLiveRangeSummary. Callers need to decide how to handle reborrows and pointer escapes. If either condition exists then the resulting liveness does not necessarily encapsulate the definition's ownership. Fixes some number of latent bugs w.r.t. liveness clients. --- include/swift/SIL/OwnershipUtils.h | 3 +- include/swift/SIL/PrunedLiveness.h | 99 +++++++++++++++++-- include/swift/SIL/ScopedAddressUtils.h | 4 + lib/SIL/Utils/OwnershipUtils.cpp | 11 ++- lib/SIL/Utils/PrunedLiveness.cpp | 52 +++++++--- lib/SIL/Utils/ScopedAddressUtils.cpp | 8 +- .../Mandatory/DiagnoseLifetimeIssues.cpp | 6 +- .../MoveKillsCopyableValuesChecker.cpp | 5 +- .../Utils/CanonicalOSSALifetime.cpp | 4 +- 9 files changed, 159 insertions(+), 33 deletions(-) diff --git a/include/swift/SIL/OwnershipUtils.h b/include/swift/SIL/OwnershipUtils.h index 2e104888611f2..aeaa67b5bd81f 100644 --- a/include/swift/SIL/OwnershipUtils.h +++ b/include/swift/SIL/OwnershipUtils.h @@ -16,7 +16,6 @@ #include "swift/Basic/Debug.h" #include "swift/Basic/LLVM.h" #include "swift/SIL/MemAccessUtils.h" -#include "swift/SIL/PrunedLiveness.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILBasicBlock.h" #include "swift/SIL/SILInstruction.h" @@ -31,7 +30,7 @@ class SILInstruction; class SILModule; class SILValue; class DeadEndBlocks; -class PrunedLiveness; +class MultiDefPrunedLiveness; struct BorrowedValue; /// Returns true if v is an address or trivial. diff --git a/include/swift/SIL/PrunedLiveness.h b/include/swift/SIL/PrunedLiveness.h index a2ce056c93578..125488f7b4829 100644 --- a/include/swift/SIL/PrunedLiveness.h +++ b/include/swift/SIL/PrunedLiveness.h @@ -91,6 +91,7 @@ #include "swift/AST/TypeExpansionContext.h" #include "swift/SIL/BasicBlockDatastructures.h" #include "swift/SIL/NodeDatastructures.h" +#include "swift/SIL/OwnershipUtils.h" #include "swift/SIL/SILBasicBlock.h" #include "swift/SIL/SILFunction.h" #include "llvm/ADT/MapVector.h" @@ -329,6 +330,51 @@ class PrunedLiveBlocks { unsigned endBitNo); }; +/// If inner borrows are 'Contained', then liveness is fully described by the +/// scope-ending instructions of any inner borrow scopes, and those scope-ending +/// uses are dominated by the current def. This is known as a "simple" live +/// range. +/// +/// If nested borrows are 'Reborrowed' then simple liveness computed here based +/// on dominated uses is not sufficient to guarantee the value's lifetime. To do +/// that, the client needs to consider the reborrow scopes. OSSALiveness handles +/// those details. +/// +/// Reborrows are only relevant when they apply to the first level of borrow +/// scope. Reborrows within nested borrows scopes are already summarized by the +/// outer borrow scope. +enum class InnerBorrowKind { + Contained, // any borrows are fully contained within this live range + Reborrowed // at least one immediately nested borrow is reborrowed +}; + +inline InnerBorrowKind meet(InnerBorrowKind lhs, InnerBorrowKind rhs) { + return (lhs > rhs) ? lhs : rhs; +} + +/// Summarize reborrows and pointer escapes that affect a live range. Reborrows +/// and pointer escapes that are encapsulated in a nested borrow don't affect +/// the outer live range. +struct SimpleLiveRangeSummary { + InnerBorrowKind innerBorrowKind; + AddressUseKind addressUseKind; + + SimpleLiveRangeSummary(): innerBorrowKind(InnerBorrowKind::Contained), + addressUseKind(AddressUseKind::NonEscaping) + {} + + void meet(const InnerBorrowKind lhs) { + innerBorrowKind = swift::meet(innerBorrowKind, lhs); + } + void meet(const AddressUseKind lhs) { + addressUseKind = swift::meet(addressUseKind, lhs); + } + void meet(const SimpleLiveRangeSummary lhs) { + meet(lhs.innerBorrowKind); + meet(lhs.addressUseKind); + } +}; + /// PrunedLiveness tracks PrunedLiveBlocks along with "interesting" use /// points. The set of interesting uses is a superset of all uses on the /// liveness boundary. Filtering out uses that are obviously not on the liveness @@ -403,8 +449,15 @@ class PrunedLiveness { 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. - bool updateForBorrowingOperand(Operand *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. + void checkAndUpdateInteriorPointer(Operand *operand); /// Update this liveness to extend across the given liveness. void extendAcrossLiveness(PrunedLiveness &otherLiveness); @@ -494,8 +547,8 @@ class PrunedLiveRange : public PrunedLiveness { public: /// Update liveness for all direct uses of \p def. - void updateForDef(SILValue def); - + SimpleLiveRangeSummary updateForDef(SILValue def); + /// Check if \p inst occurs in between the definition this def and the /// liveness boundary. bool isWithinBoundary(SILInstruction *inst) const; @@ -613,11 +666,27 @@ class SSAPrunedLiveness : public PrunedLiveRange { } /// Compute liveness for a single SSA definition. The lifetime-ending uses are - /// also recorded--destroy_value or end_borrow. However destroy_values might - /// not jointly-post dominate if dead-end blocks are present. - void compute() { + /// also recorded--destroy_value or end_borrow. + /// + /// This only handles simple liveness in which this definition reaches all + /// uses without involving phis. If the returned summary includes + /// InnerBorrowKind::Reborrow, then the potentially non-dominated uses are + /// not included. + /// + /// This only handles simple liveness in which all uses are dominated by the + /// definition. If the returned summary includes InnerBorrowKind::Reborrow, + /// then the resulting liveness does not includes potentially non-dominated + /// uses within the reborrow scope. If the summary returns something other + /// than AddressUseKind::NonEscaping, then the resulting liveness does not + /// necessarilly encapsulate value ownership. + /// + /// Warning: If OSSA lifetimes are incomplete, then destroy_values might not + /// jointly-post dominate if dead-end blocks are present. Nested scopes may + /// also lack scope-ending instructions, so the liveness of their nested uses + /// may be ignored. + SimpleLiveRangeSummary computeSimple() { assert(def && "SSA def uninitialized"); - updateForDef(def); + return updateForDef(def); } }; @@ -683,7 +752,19 @@ class MultiDefPrunedLiveness : public PrunedLiveRange { /// lifetime-ending uses are also recorded--destroy_value or /// end_borrow. However destroy_values might not jointly-post dominate if /// dead-end blocks are present. - void compute(); + /// + /// This only handles simple liveness in which all uses are dominated by the + /// definition. If the returned summary includes InnerBorrowKind::Reborrow, + /// then the resulting liveness does not includes potentially non-dominated + /// uses within the reborrow scope. If the summary returns something other + /// than AddressUseKind::NonEscaping, then the resulting liveness does not + /// necessarilly encapsulate value ownership. + /// + /// Warning: If OSSA lifetimes are incomplete, then destroy_values might not + /// jointly-post dominate if dead-end blocks are present. Nested scopes may + /// also lack scope-ending instructions, so the liveness of their nested uses + /// may be ignored. + SimpleLiveRangeSummary computeSimple(); }; //===----------------------------------------------------------------------===// diff --git a/include/swift/SIL/ScopedAddressUtils.h b/include/swift/SIL/ScopedAddressUtils.h index ad9de9f87e75b..1d88a0fcc7d02 100644 --- a/include/swift/SIL/ScopedAddressUtils.h +++ b/include/swift/SIL/ScopedAddressUtils.h @@ -88,11 +88,15 @@ struct ScopedAddressValue { bool isScopeEndingUse(Operand *op) const; /// Pass all scope ending instructions to the visitor. bool visitScopeEndingUses(function_ref visitor) const; + /// Optimistically computes liveness for all known uses, and adds this scope's /// live blocks into the SSA PrunedLiveness result. Returns AddressUseKind /// indicated whether a PointerEscape or Unknown use was encountered. AddressUseKind computeLiveness(SSAPrunedLiveness &liveness) const; + /// Update \p liveness for all the address uses. + AddressUseKind updateLiveness(PrunedLiveness &liveness) const; + /// Create appropriate scope ending instruction at \p insertPt. void createScopeEnd(SILBasicBlock::iterator insertPt, SILLocation loc) const; diff --git a/lib/SIL/Utils/OwnershipUtils.cpp b/lib/SIL/Utils/OwnershipUtils.cpp index c57b981f695fd..9a71709a0c632 100644 --- a/lib/SIL/Utils/OwnershipUtils.cpp +++ b/lib/SIL/Utils/OwnershipUtils.cpp @@ -1089,12 +1089,19 @@ bool AddressOwnership::areUsesWithinLifetime( if (borrow) return borrow.areUsesWithinTransitiveScope(uses, &deadEndBlocks); - // --- A reference no borrow scope. Currently happens for project_box. + // --- A reference with no borrow scope! Currently happens for project_box. // Compute the reference value's liveness. SSAPrunedLiveness liveness; liveness.initializeDef(root); - liveness.compute(); + SimpleLiveRangeSummary summary = liveness.computeSimple(); + // Conservatively ignore InnerBorrowKind::Reborrowed and + // AddressUseKind::PointerEscape and Reborrowed. The resulting liveness at + // least covers the known uses. + (void)summary; + + // FIXME (implicit borrow): handle reborrows transitively just like above so + // we don't bail out if a uses is within the reborrowed scope. return liveness.areUsesWithinBoundary(uses, &deadEndBlocks); } diff --git a/lib/SIL/Utils/PrunedLiveness.cpp b/lib/SIL/Utils/PrunedLiveness.cpp index 277573df08396..d8d518443ace4 100644 --- a/lib/SIL/Utils/PrunedLiveness.cpp +++ b/lib/SIL/Utils/PrunedLiveness.cpp @@ -16,6 +16,7 @@ #include "swift/SIL/BasicBlockDatastructures.h" #include "swift/SIL/BasicBlockUtils.h" #include "swift/SIL/OwnershipUtils.h" +#include "swift/SIL/ScopedAddressUtils.h" #include "swift/SIL/SILInstruction.h" using namespace swift; @@ -111,8 +112,8 @@ void PrunedLiveness::updateForUse(SILInstruction *user, bool lifetimeEnding) { iterAndSuccess.first->second &= lifetimeEnding; } -bool PrunedLiveness::updateForBorrowingOperand(Operand *op) { - assert(op->getOperandOwnership() == OperandOwnership::Borrow); +InnerBorrowKind PrunedLiveness::updateForBorrowingOperand(Operand *operand) { + assert(operand->getOperandOwnership() == OperandOwnership::Borrow); // A nested borrow scope is considered a use-point at each scope ending // instruction. @@ -120,16 +121,31 @@ bool PrunedLiveness::updateForBorrowingOperand(Operand *op) { // TODO: Handle reborrowed copies by considering the extended borrow // scope. Temporarily bail-out on reborrows because we can't handle uses // that aren't dominated by currentDef. - if (!BorrowingOperand(op).visitScopeEndingUses([this](Operand *end) { + if (!BorrowingOperand(operand).visitScopeEndingUses([this](Operand *end) { if (end->getOperandOwnership() == OperandOwnership::Reborrow) { return false; } updateForUse(end->getUser(), /*lifetimeEnding*/ false); return true; })) { - return false; + return InnerBorrowKind::Reborrowed; } - return true; + return InnerBorrowKind::Contained; +} + +void PrunedLiveness::checkAndUpdateInteriorPointer(Operand *operand) { + assert(operand->getOperandOwnership() == OperandOwnership::InteriorPointer); + + if (auto *svi = dyn_cast(operand->getUser())) { + if (auto scopedAddress = ScopedAddressValue(svi)) { + scopedAddress.visitScopeEndingUses([this](Operand *end) { + updateForUse(end->getUser(), /*lifetimeEnding*/ false); + return true; + }); + return; + } + } + updateForUse(operand->getUser(), /*lifetimeEnding*/ false); } void PrunedLiveness::extendAcrossLiveness(PrunedLiveness &otherLivesness) { @@ -240,7 +256,8 @@ void PrunedLivenessBoundary::visitInsertionPoints( //===----------------------------------------------------------------------===// template -void PrunedLiveRange::updateForDef(SILValue def) { +SimpleLiveRangeSummary PrunedLiveRange::updateForDef(SILValue def) { + SimpleLiveRangeSummary summary; // Note: Uses with OperandOwnership::NonUse cannot be considered normal uses // for liveness. Otherwise, liveness would need to separately track non-uses // everywhere. Non-uses cannot be treated like normal non-lifetime-ending uses @@ -250,16 +267,23 @@ void PrunedLiveRange::updateForDef(SILValue def) { // type-dependent operands exist. for (Operand *use : def->getUses()) { switch (use->getOperandOwnership()) { - default: - updateForUse(use->getUser(), use->isLifetimeEnding()); - break; case OperandOwnership::NonUse: break; case OperandOwnership::Borrow: - updateForBorrowingOperand(use); + summary.meet(updateForBorrowingOperand(use)); + break; + case OperandOwnership::PointerEscape: + summary.meet(AddressUseKind::PointerEscape); + break; + case OperandOwnership::InteriorPointer: + checkAndUpdateInteriorPointer(use); + break; + default: + updateForUse(use->getUser(), use->isLifetimeEnding()); break; } } + return summary; } template @@ -457,18 +481,20 @@ MultiDefPrunedLiveness::findPreviousDef(SILInstruction *searchPos, return nullptr; } -void MultiDefPrunedLiveness::compute() { +SimpleLiveRangeSummary MultiDefPrunedLiveness::computeSimple() { assert(isInitialized() && "defs uninitialized"); + SimpleLiveRangeSummary summary; for (SILNode *defNode : defs) { if (auto *arg = dyn_cast(defNode)) - updateForDef(arg); + summary.meet(updateForDef(arg)); else { for (auto result : cast(defNode)->getResults()) { - updateForDef(result); + summary.meet(updateForDef(result)); } } } + return summary; } //===----------------------------------------------------------------------===// diff --git a/lib/SIL/Utils/ScopedAddressUtils.cpp b/lib/SIL/Utils/ScopedAddressUtils.cpp index 119da176c1ec5..15489842f761b 100644 --- a/lib/SIL/Utils/ScopedAddressUtils.cpp +++ b/lib/SIL/Utils/ScopedAddressUtils.cpp @@ -101,11 +101,15 @@ bool ScopedAddressValue::visitScopeEndingUses( // AddressUseKind ScopedAddressValue:: computeLiveness(SSAPrunedLiveness &liveness) const { + liveness.initializeDef(value); + return updateLiveness(liveness); +} + +AddressUseKind ScopedAddressValue:: +updateLiveness(PrunedLiveness &liveness) const { SmallVector uses; // Collect all uses that need to be enclosed by the scope. auto addressKind = findTransitiveUsesForAddress(value, &uses); - - liveness.initializeDef(value); for (auto *use : uses) { // Update all collected uses as non-lifetime ending. liveness.updateForUse(use->getUser(), /* lifetimeEnding */ false); diff --git a/lib/SILOptimizer/Mandatory/DiagnoseLifetimeIssues.cpp b/lib/SILOptimizer/Mandatory/DiagnoseLifetimeIssues.cpp index e8377d7f0cf65..7afb598e519bd 100644 --- a/lib/SILOptimizer/Mandatory/DiagnoseLifetimeIssues.cpp +++ b/lib/SILOptimizer/Mandatory/DiagnoseLifetimeIssues.cpp @@ -238,9 +238,11 @@ visitUses(SILValue def, bool updateLivenessAndWeakStores, int callDepth) { return CanEscape; break; case OperandOwnership::Borrow: { - if (updateLivenessAndWeakStores && - !liveness.updateForBorrowingOperand(use)) + if (updateLivenessAndWeakStores + && (liveness.updateForBorrowingOperand(use) + != InnerBorrowKind::Contained)) { return CanEscape; + } BorrowingOperand borrowOper(use); if (borrowOper.hasBorrowIntroducingUser()) { if (auto *beginBorrow = dyn_cast(user)) diff --git a/lib/SILOptimizer/Mandatory/MoveKillsCopyableValuesChecker.cpp b/lib/SILOptimizer/Mandatory/MoveKillsCopyableValuesChecker.cpp index 84ea75b79feed..0386ca990bbde 100644 --- a/lib/SILOptimizer/Mandatory/MoveKillsCopyableValuesChecker.cpp +++ b/lib/SILOptimizer/Mandatory/MoveKillsCopyableValuesChecker.cpp @@ -152,9 +152,10 @@ bool CheckerLivenessInfo::compute() { // Otherwise, try to update liveness for a borrowing operand // use. This will make it so that we add the end_borrows of the // liveness use. If we have a reborrow here, we will bail. - bool failed = !liveness.updateForBorrowingOperand(use); - if (failed) + if (liveness.updateForBorrowingOperand(use) + != InnerBorrowKind::Contained) { return false; + } } } break; diff --git a/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp b/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp index d37df09afa4bd..e9e968808b5a2 100644 --- a/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp +++ b/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp @@ -166,8 +166,10 @@ bool CanonicalizeOSSALifetime::computeCanonicalLiveness() { recordConsumingUse(use); break; case OperandOwnership::Borrow: - if (!liveness.updateForBorrowingOperand(use)) + if (liveness.updateForBorrowingOperand(use) + != InnerBorrowKind::Contained) { return false; + } break; case OperandOwnership::InteriorPointer: case OperandOwnership::ForwardingBorrow: From 619a638e3462335b6b97801fad8d1fdebb450ee6 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Wed, 28 Sep 2022 23:09:32 -0700 Subject: [PATCH 06/19] ScopedAddressUtils - clarify the API used for transitive liveness. Begin to distnguish logic that relies on complete OSSA lifetimes from the logic that computes transitive uses. --- include/swift/SIL/ScopedAddressUtils.h | 26 +++++++++++++++++------- lib/SIL/Utils/ScopedAddressUtils.cpp | 26 +++++++++++++++--------- lib/SIL/Verifier/SILVerifier.cpp | 3 ++- lib/SILOptimizer/Utils/GenericCloner.cpp | 6 +++++- 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/include/swift/SIL/ScopedAddressUtils.h b/include/swift/SIL/ScopedAddressUtils.h index 1d88a0fcc7d02..dacd8c6c19c26 100644 --- a/include/swift/SIL/ScopedAddressUtils.h +++ b/include/swift/SIL/ScopedAddressUtils.h @@ -89,13 +89,25 @@ struct ScopedAddressValue { /// Pass all scope ending instructions to the visitor. bool visitScopeEndingUses(function_ref visitor) const; - /// Optimistically computes liveness for all known uses, and adds this scope's - /// live blocks into the SSA PrunedLiveness result. Returns AddressUseKind - /// indicated whether a PointerEscape or Unknown use was encountered. - AddressUseKind computeLiveness(SSAPrunedLiveness &liveness) const; - - /// Update \p liveness for all the address uses. - AddressUseKind updateLiveness(PrunedLiveness &liveness) const; + /// Optimistically computes liveness for all transitive uses, and adds this + /// scope's live blocks into the SSA PrunedLiveness result. Returns + /// AddressUseKind indicated whether a PointerEscape or Unknown use was + /// encountered. + /// + /// This transitively finds uses within nested borrow scopes to handle + /// incomplete nested lifetimes. Here, liveness will consider the apply to be + /// a live use of the store_borrow: + /// %a = store_borrow + /// %v = load_borrow + /// apply (%v) + /// unreachable + /// + /// FIXME: with complete OSSA lifetimes, store borrow liveness is simply + /// computed by visiting the end_borrow users. + AddressUseKind computeTransitiveLiveness(SSAPrunedLiveness &liveness) const; + + /// Update \p liveness for all the transitive address uses. + AddressUseKind updateTransitiveLiveness(PrunedLiveness &liveness) const; /// Create appropriate scope ending instruction at \p insertPt. void createScopeEnd(SILBasicBlock::iterator insertPt, SILLocation loc) const; diff --git a/lib/SIL/Utils/ScopedAddressUtils.cpp b/lib/SIL/Utils/ScopedAddressUtils.cpp index 15489842f761b..05cf06f160908 100644 --- a/lib/SIL/Utils/ScopedAddressUtils.cpp +++ b/lib/SIL/Utils/ScopedAddressUtils.cpp @@ -90,27 +90,30 @@ bool ScopedAddressValue::visitScopeEndingUses( // scope's lifetime is already complete. Therefore, it needs to transitively // process all address uses. // -// FIXME: this should use the standard recursive lifetime completion -// utility. Otherwise dealing with nested incomplete lifetimes becomes expensive -// and complex. e.g. +// FIXME: users of this should use the standard recursive lifetime completion +// utility. Otherwise dealing with nested incomplete lifetimes becomes +// expensive and complex. e.g. // // %storeBorrow = store_borrow %_ to %adr // %loadBorrow = load_borrow %storeBorrow // apply %f(%loadBorrow) : $@convention(thin) (...) -> Never // unreachable // -AddressUseKind ScopedAddressValue:: -computeLiveness(SSAPrunedLiveness &liveness) const { +AddressUseKind ScopedAddressValue::computeTransitiveLiveness( + SSAPrunedLiveness &liveness) const { liveness.initializeDef(value); - return updateLiveness(liveness); + return updateTransitiveLiveness(liveness); } -AddressUseKind ScopedAddressValue:: -updateLiveness(PrunedLiveness &liveness) const { +AddressUseKind +ScopedAddressValue::updateTransitiveLiveness(PrunedLiveness &liveness) const { SmallVector uses; // Collect all uses that need to be enclosed by the scope. auto addressKind = findTransitiveUsesForAddress(value, &uses); for (auto *use : uses) { + if (isScopeEndingUse(use)) + continue; + // Update all collected uses as non-lifetime ending. liveness.updateForUse(use->getUser(), /* lifetimeEnding */ false); } @@ -187,8 +190,11 @@ bool swift::extendStoreBorrow(StoreBorrowInst *sbi, SmallVector discoveredBlocks; SSAPrunedLiveness storeBorrowLiveness(&discoveredBlocks); - - AddressUseKind useKind = scopedAddress.computeLiveness(storeBorrowLiveness); + + // FIXME: if OSSA lifetimes are complete, then we don't need transitive + // liveness here. + AddressUseKind useKind = + scopedAddress.computeTransitiveLiveness(storeBorrowLiveness); // If all new uses are within store_borrow boundary, no need for extension. if (storeBorrowLiveness.areUsesWithinBoundary(newUses, deadEndBlocks)) { diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index 856627df3a3da..cfb6c20dad099 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -2446,8 +2446,9 @@ class SILVerifier : public SILVerifierBase { SSAPrunedLiveness scopedAddressLiveness; ScopedAddressValue scopedAddress(SI); + // FIXME: with complete lifetimes, this should not be a transitive check. AddressUseKind useKind = - scopedAddress.computeLiveness(scopedAddressLiveness); + scopedAddress.computeTransitiveLiveness(scopedAddressLiveness); bool success = useKind == AddressUseKind::NonEscaping; require(!success || checkScopedAddressUses( diff --git a/lib/SILOptimizer/Utils/GenericCloner.cpp b/lib/SILOptimizer/Utils/GenericCloner.cpp index 3cda3b21c7138..4f65d7bd681d9 100644 --- a/lib/SILOptimizer/Utils/GenericCloner.cpp +++ b/lib/SILOptimizer/Utils/GenericCloner.cpp @@ -250,8 +250,12 @@ void GenericCloner::postFixUp(SILFunction *f) { continue; } discoveredBlocks.clear(); + // FIXME: Call OSSA lifetime fixup on all values used within the unreachable + // code. This will recursively fixup nested scopes from the inside out so + // that transitive liveness is not required. SSAPrunedLiveness storeBorrowLiveness(&discoveredBlocks); - AddressUseKind useKind = scopedAddress.computeLiveness(storeBorrowLiveness); + AddressUseKind useKind = + scopedAddress.computeTransitiveLiveness(storeBorrowLiveness); if (useKind == AddressUseKind::NonEscaping) { scopedAddress.endScopeAtLivenessBoundary(&storeBorrowLiveness); continue; From 376483db82c4a516d9fa444587f4e0cce61b8c80 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Fri, 23 Sep 2022 21:25:13 -0700 Subject: [PATCH 07/19] Add a test case for generic specialization with borrow scope fixup. This tests a borrow scope whose use becomes unreachable. The liveness utilities need to work on this malformed SIL in order to fixup OSSA on-the-fly. --- test/SILOptimizer/specialize_ossa.sil | 41 +++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/SILOptimizer/specialize_ossa.sil b/test/SILOptimizer/specialize_ossa.sil index 832c37f6d09bb..1911a19f3ebb5 100644 --- a/test/SILOptimizer/specialize_ossa.sil +++ b/test/SILOptimizer/specialize_ossa.sil @@ -1287,3 +1287,44 @@ bb0(%0 : $Builtin.Int32, %1 : @guaranteed $Builtin.NativeObject): %9999 = tuple() return %9999 : $() } + +sil @genericReturn : $@convention(thin) <τ_0_0> (@guaranteed AnyObject) -> @out τ_0_0 + +// rdar://99874173: assert; PrunedLiveness; No user in LiveWithin block +// +// end_borrow becomes unreachable after specializing genericReturn with Never. +// +// The generic cloner needs to be able to compute liveness to fixup a +// store borrow scope when the only use is in unreachable code. +// specialized testNoReturnSpecialization +// CHECK-LABEL: sil shared [ossa] @$s26testNoReturnSpecializations5NeverO_Tg5 : $@convention(thin) (@guaranteed AnyObject, @thick Never.Type) -> () { +// CHECK: [[SB:%.*]] = store_borrow %0 to %{{.*}} : $*AnyObject +// CHECK: [[LB:%.*]] = load_borrow [[SB]] : $*AnyObject +// CHECK: apply %{{.*}}(%{{.*}}, [[LB]]) : $@convention(thin) <τ_0_0> (@guaranteed AnyObject) -> @out τ_0_0 +// CHECK: end_borrow [[SB]] : $*AnyObject +// CHECK: unreachable +// CHECK: bb1: +// CHECK: end_borrow [[LB]] : $AnyObject +// CHECK: end_borrow [[SB]] : $*AnyObject +// CHECK-LABEL: } // end sil function '$s26testNoReturnSpecializations5NeverO_Tg5' +sil [ossa] @testNoReturnSpecialization : $@convention(thin) (@in_guaranteed AnyObject, @thick T.Type) -> () { +bb0(%0 : $*AnyObject, %1 : $@thick T.Type): + %2 = load_borrow %0 : $*AnyObject + %f = function_ref @genericReturn : $@convention(thin) <τ_0_0> (@guaranteed AnyObject) -> @out τ_0_0 + %out = alloc_stack $T + %dummy = apply %f(%out, %2) : $@convention(thin) <τ_0_0> (@guaranteed AnyObject) -> @out τ_0_0 + destroy_addr %out : $*T + dealloc_stack %out : $*T + end_borrow %2 : $AnyObject + %99 = tuple () + return %99 : $() +} + +sil [ossa] @testCallNoReturnSpecialization : $@convention(thin) (@in_guaranteed AnyObject) -> () { +bb0(%0 : $*AnyObject): + %f = function_ref @testNoReturnSpecialization : $@convention(thin) (@in_guaranteed AnyObject, @thick T.Type) -> () + %m = metatype $@thick Never.Type + %r = apply %f(%0, %m) : $@convention(thin) (@in_guaranteed AnyObject, @thick T.Type) -> () + %t = tuple () + return %t : $() +} From 11ef752a8a77d942661b9298681d9fb5facdf03a Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Thu, 29 Sep 2022 11:32:02 -0700 Subject: [PATCH 08/19] Add an OSSLifetimeAnalysis utility pass Supports unit testing for OSSA lifetime utilities. These utilities are the basis of maintaining valid OSSA form. They need to handle invalid SIL (with incomplete OSSA lifetimes) because we rely on them to fixup SIL after SILGen and after any transformation that may affect ownership. --- .../swift/SILOptimizer/PassManager/Passes.def | 2 + lib/SILOptimizer/UtilityPasses/CMakeLists.txt | 1 + .../UtilityPasses/OSSALifetimeAnalysis.cpp | 139 ++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 lib/SILOptimizer/UtilityPasses/OSSALifetimeAnalysis.cpp diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index 2a9df82a420df..a204408029b49 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -332,6 +332,8 @@ SWIFT_FUNCTION_PASS(ObjCBridgingOptimization, "objc-bridging-opt", "Optimize ObjectiveC briging operations") PASS(ObjectOutliner, "object-outliner", "Outlining of Global Objects") +PASS(OSSALifetimeAnalysis, "ossa-lifetime-analysis", + "Dump OSSA Lifetimes for Traced Values") PASS(Outliner, "outliner", "Function Outlining Optimization") PASS(OwnershipModelEliminator, "ownership-model-eliminator", diff --git a/lib/SILOptimizer/UtilityPasses/CMakeLists.txt b/lib/SILOptimizer/UtilityPasses/CMakeLists.txt index f1c19514fdd94..f8e19c3d683a5 100644 --- a/lib/SILOptimizer/UtilityPasses/CMakeLists.txt +++ b/lib/SILOptimizer/UtilityPasses/CMakeLists.txt @@ -26,6 +26,7 @@ target_sources(swiftSILOptimizer PRIVATE LoopRegionPrinter.cpp MemBehaviorDumper.cpp ModulePrinter.cpp + OSSALifetimeAnalysis.cpp RCIdentityDumper.cpp SerializeSILPass.cpp SILDebugInfoGenerator.cpp diff --git a/lib/SILOptimizer/UtilityPasses/OSSALifetimeAnalysis.cpp b/lib/SILOptimizer/UtilityPasses/OSSALifetimeAnalysis.cpp new file mode 100644 index 0000000000000..66131dbdcf81c --- /dev/null +++ b/lib/SILOptimizer/UtilityPasses/OSSALifetimeAnalysis.cpp @@ -0,0 +1,139 @@ +//===--- OSSALifetimeAnalysis.cpp - OSSA lifetime analysis ----------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 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 +// +//===----------------------------------------------------------------------===// +/// +/// This currently runs SSAPrunedLiveness on SSA values, MultiDefPrunedLiveness +/// on phi webs, and MultiDefPrunedLiveness on 'copy_addr [init]' and 'store +/// [init]'. +/// +/// Soon, it will also run OSSALiveness on OSSA values. +//===----------------------------------------------------------------------===// + +#define DEBUG_TYPE "ossa-lifetime-analysis" +#include "swift/SIL/PrunedLiveness.h" +#include "swift/SIL/SILArgument.h" +#include "swift/SIL/SILFunction.h" +#include "swift/SIL/SILInstruction.h" +#include "swift/SIL/SILValue.h" +#include "swift/SIL/ScopedAddressUtils.h" +#include "swift/SILOptimizer/PassManager/Passes.h" +#include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Utils/InstructionDeleter.h" +#include "llvm/Support/Debug.h" + +using namespace swift; + +namespace { + +struct OSSALifetimeAnalyzer { + SILFunction *function; + + OSSALifetimeAnalyzer(SILFunction *function): function(function) {} + + void analyzeSSAValue(SILValue value); + + void analyzeScopedAddress(ScopedAddressValue scopedAddress); + + void analyzeValues(ArrayRef values); +}; + +void OSSALifetimeAnalyzer::analyzeSSAValue(SILValue value) { + llvm::outs() << "\nSSA lifetime analysis: " << value; + SmallVector discoveredBlocks; + SSAPrunedLiveness liveness(&discoveredBlocks); + liveness.initializeDef(value); + liveness.computeSimple(); + liveness.print(llvm::outs()); + + PrunedLivenessBoundary boundary; + liveness.computeBoundary(boundary); + boundary.print(llvm::outs()); +} + +void OSSALifetimeAnalyzer::analyzeScopedAddress( + ScopedAddressValue scopedAddress) { + llvm::outs() << "\nScoped address analysis: " << scopedAddress.value; + SmallVector discoveredBlocks; + SSAPrunedLiveness liveness(&discoveredBlocks); + scopedAddress.computeTransitiveLiveness(liveness); + liveness.print(llvm::outs()); + + PrunedLivenessBoundary boundary; + liveness.computeBoundary(boundary); + boundary.print(llvm::outs()); +} + +void OSSALifetimeAnalyzer::analyzeValues(ArrayRef values) { + llvm::outs() << "\nMultiDef lifetime analysis:\n"; + SmallVector discoveredBlocks; + MultiDefPrunedLiveness liveness(function, &discoveredBlocks); + for (SILValue value : values) { + llvm::outs() << " def: " << value; + liveness.initializeDef(value); + } + liveness.computeSimple(); + liveness.print(llvm::outs()); + + PrunedLivenessBoundary boundary; + liveness.computeBoundary(boundary); + boundary.print(llvm::outs()); +} + +class OSSALifetimeAnalysis : public SILModuleTransform { + void run() override; +}; + +// Find and record all debug_value [trace] instructions. Strip them before +// querying liveness so we can test dead values. Alternatively, we could +// override SSAPrunedLiveness::compute above and simply ignore trace +// instructions. +// +// Consider each traced value first as an indivual live range, then consider +// them all together as a single live range. +void OSSALifetimeAnalysis::run() { + for (auto &function : *getModule()) { + llvm::outs() << "\n@" << function.getName() << "\n"; + if (function.empty()) { + llvm::outs() << "\n"; + continue; + } + SmallVector traceValues; + InstructionDeleter deleter; + for (auto &block : function) { + for (SILInstruction *inst : deleter.updatingRange(&block)) { + if (auto *debugValue = dyn_cast(inst)) { + traceValues.push_back(debugValue->getOperand()); + deleter.forceDelete(debugValue); + } + } + } + OSSALifetimeAnalyzer analyzer(&function); + for (auto value : traceValues) { + if (value->getType().isAddress()) { + ScopedAddressValue scopedAddress(value); + if (scopedAddress) { + analyzer.analyzeScopedAddress(scopedAddress); + continue; + } + } + analyzer.analyzeSSAValue(value); + } + if (traceValues.size() > 1) { + analyzer.analyzeValues(traceValues); + } + } +} + +} // end anonymous namespace + +SILTransform *swift::createOSSALifetimeAnalysis() { + return new OSSALifetimeAnalysis(); +} From 6daebfdf192520657448df24c28491a64a9f4eef Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Thu, 29 Sep 2022 11:33:35 -0700 Subject: [PATCH 09/19] Add ossa lifetime analysis tests --- test/SILOptimizer/ossa_lifetime_analysis.sil | 111 +++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 test/SILOptimizer/ossa_lifetime_analysis.sil diff --git a/test/SILOptimizer/ossa_lifetime_analysis.sil b/test/SILOptimizer/ossa_lifetime_analysis.sil new file mode 100644 index 0000000000000..ed7f68f2c8eb7 --- /dev/null +++ b/test/SILOptimizer/ossa_lifetime_analysis.sil @@ -0,0 +1,111 @@ +// RUN: %target-sil-opt %s -ossa-lifetime-analysis -o /dev/null | %FileCheck %s +// +// These tests rely on "incomplete" OSSA lifetimes. Incomplete OSSA +// lifetimes are invalid SIL, but the OSSA liveness utilities still +// need to handle them! SILGen and textual SIL is allowed to produce +// incomplete lifetimes. The OSSA liveness utilities need to be able +// to fixup those incomplete lifetimes. So the utilities need to +// handle invalid SIL in order to produce valid SIL. +// +// For each function, all values used by debug_value [trace] are +// considered live range defs. Liveness is computed from their direct uses. + +sil_stage canonical + +import Builtin + +enum FakeOptional { +case none +case some(T) +} + +class C {} + +// CHECK-LABEL: @testSelfLoop +// CHECK: SSA lifetime analysis: [[V:%.*]] = copy_value %0 : $C +// CHECK: bb1: LiveOut +// CHECK: bb2: LiveWithin +// CHECK: lifetime-ending user: br bb1([[V]] : $C) +// CHECK: last user: br bb1([[V]] : $C) +// CHECK-NOT: dead def +sil [ossa] @testSelfLoop : $@convention(thin) (@guaranteed C) -> () { +bb0(%0 : @guaranteed $C): + br bb3 + +bb1(%1 : @owned $C): + destroy_value %1 : $C + %2 = copy_value %0 : $C + debug_value [trace] %2 : $C + br bb2 + +bb2: + br bb1(%2 : $C) + +bb3: + %99 = tuple() + return %99 : $() +} + +// CHECK-LABEL: @testSelfKill +// CHECK:SSA lifetime analysis: [[V:%.*]] = move_value %1 : $C +// CHECK: bb1: LiveOut +// CHECK: bb2: LiveWithin +// CHECK: lifetime-ending user: br bb1([[V]] : $C) +// CHECK: last user: br bb1([[V]] : $C) +// CHECK-NOT: dead def +sil [ossa] @testSelfKill : $@convention(thin) () -> () { +bb0: + br bb3 + +bb1(%1 : @owned $C): + %2 = move_value %1 : $C + debug_value [trace] %2 : $C + br bb2 + +bb2: + br bb1(%2 : $C) + +bb3: + %99 = tuple() + return %99 : $() +} + +// Test a live range that is extended through reborrows, +// considering them new defs. +// (e.g. BorrowedValue::visitTransitiveLifetimeEndingUses) +// +// This live range is not dominated by the original borrow. +// +// CHECK-LABEL: @testReborrow +// CHECK: MultiDef lifetime analysis: +// CHECK: def: [[B:%.*]] = begin_borrow %0 : $C +// CHECK: def: [[RB:%.*]] = argument of bb3 : $C +// CHECK-NEXT: bb2: LiveWithin +// CHECK-NEXT: bb3: LiveWithin +// CHECK-NEXT: lifetime-ending user: br bb3([[B]] : $C) +// CHECK-NEXT: lifetime-ending user: end_borrow [[RB]] : $C +// CHECK-NEXT: last user: br bb3([[B]] : $C) +// CHECK-NEXT: last user: end_borrow [[RB]] : $C +sil [ossa] @testReborrow : $@convention(thin) (@owned C) -> () { +bb0(%0 : @owned $C): + cond_br undef, bb1, bb2 + +bb1: + %borrow1 = begin_borrow %0 : $C + br bb3(%borrow1 : $C) + +bb2: + %borrow2 = begin_borrow %0 : $C + debug_value [trace] %borrow2 : $C + br bb3(%borrow2 : $C) + +bb3(%reborrow : @guaranteed $C): + debug_value [trace] %reborrow : $C + end_borrow %reborrow : $C + br bb4 + +bb4: + destroy_value %0 : $C + %99 = tuple() + return %99 : $() +} From 3b5e19850298feb13d9cc258e7827fbd17522ed1 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Thu, 29 Sep 2022 11:33:55 -0700 Subject: [PATCH 10/19] Add OSSA liveness tests for incomplete lifetimes These tests all involve unreachable terminators. --- .../ossa_incomplete_lifetimes.sil | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 test/SILOptimizer/ossa_incomplete_lifetimes.sil diff --git a/test/SILOptimizer/ossa_incomplete_lifetimes.sil b/test/SILOptimizer/ossa_incomplete_lifetimes.sil new file mode 100644 index 0000000000000..a5e294372c1e7 --- /dev/null +++ b/test/SILOptimizer/ossa_incomplete_lifetimes.sil @@ -0,0 +1,120 @@ +// RUN: %target-sil-opt %s -ossa-lifetime-analysis -o /dev/null | %FileCheck %s +// +// These tests rely on "incomplete" OSSA lifetimes. Incomplete OSSA +// lifetimes are invalid SIL, but the OSSA liveness utilities still +// need to handle them! SILGen and textual SIL is allowed to produce +// incomplete lifetimes. The OSSA liveness utilities need to be able +// to fixup those incomplete lifetimes. So the utilities need to +// handle invalid SIL in order to produce valid SIL. + +sil_stage canonical + +import Builtin + +enum FakeOptional { +case none +case some(T) +} + +enum Never {} + +class C {} + +// CHECK-LABEL: @testDeadTerminatorResult +// CHECK-LABEL: SSA lifetime analysis: %{{.*}} = argument of bb1 : $C +// CHECK: bb1: LiveWithin +// CHECK-NOT: "last user" +// CHECK-NOT: "boundary edge" +// CHECK: dead def: %{{.*}} = argument of bb1 : $C +sil [ossa] @testDeadTerminatorResult : $@convention(thin) (@owned FakeOptional) -> () { +bb0(%0 : @owned $FakeOptional): + switch_enum %0 : $FakeOptional, case #FakeOptional.some!enumelt: bb1, case #FakeOptional.none!enumelt: bb2 + +bb1(%payload : @owned $C): + debug_value [trace] %payload : $C + unreachable + +bb2: + %99 = tuple() + return %99 : $() +} + +// CHECK-LABEL: @testDeadPhi +// CHECK: SSA lifetime analysis: %{{.*}} = argument of bb3 : $C +// CHECK: bb3: LiveWithin +// CHECK: dead def: %{{.*}} = argument of bb3 : $C +sil [ossa] @testDeadPhi : $@convention(thin) (@owned C, @owned C) -> () { +bb0(%0 : @owned $C, %1 : @owned $C): + cond_br undef, bb1, bb2 + +bb1: + br bb3(%0 : $C) + +bb2: + br bb3(%1 : $C) + +bb3(%2 : @owned $C): + debug_value [trace] %2 : $C + unreachable +} + +// CHECK-LABEL: @testDeadInstruction +// CHECK: SSA lifetime analysis: %{{.*}} = copy_value %0 : $C +// CHECK: bb0: LiveWithin +// CHECK: dead def: %{{.*}} = copy_value %0 : $C +sil [ossa] @testDeadInstruction : $@convention(thin) (@guaranteed C) -> () { +bb0(%0 : @guaranteed $C): + %1 = copy_value %0 : $C + debug_value [trace] %1 : $C + unreachable +} + +// A single instruction occurs twice on the same liveness +// boundary. Once as a last use, and once as a dead def. +// This is a particularly problematic corner case. +// +// CHECK-LABEL: @testDeadSelfKill +// CHECK: MultiDef lifetime analysis: +// CHECK: def: %1 = argument of bb1 : $C +// CHECK: def: [[V:%.*]] = move_value %1 : $C +// CHECK: bb1: LiveWithin +// CHECK: lifetime-ending user: [[V]] = move_value %1 : $C +// CHECK: last user: [[V]] = move_value %1 : $C +// CHECK: dead def: [[V]] = move_value %1 : $C +sil [ossa] @testDeadSelfKill : $@convention(thin) () -> () { +bb0: + br bb3 + +bb1(%1 : @owned $C): + debug_value [trace] %1 : $C + %2 = move_value %1 : $C + debug_value [trace] %2 : $C + unreachable + +bb3: + %99 = tuple() + return %99 : $() +} + +sil @genericReturn : $@convention(thin) <τ_0_0> (@guaranteed C) -> @out τ_0_0 + +// The store_borrow scope has an inner load_borrow scope, which is +// incomplete. Since the store_borrow produces an address, the load +// borrow is considered a ScopedAddressUse, and all of its uses, +// including the Apply will contribute to the store_borrow liveness. +// CHECK-LABEL: @testInnerUnreachable +// CHECK: Scoped address analysis: [[SB:%.*]] = store_borrow %0 +// CHECK: bb0: LiveWithin +// CHECK-NEXT: regular user: %{{.*}} = apply +// CHECK-NEXT: last user: %{{.*}} = apply +sil shared [ossa] @testInnerUnreachable : $@convention(thin) (@guaranteed C, @thick Never.Type) -> () { +bb0(%0 : @guaranteed $C, %1 : $@thick Never.Type): + %2 = alloc_stack $C + %3 = store_borrow %0 to %2 : $*C + debug_value [trace] %3 : $*C + %5 = load_borrow %3 : $*C + %6 = function_ref @genericReturn : $@convention(thin) <τ_0_0> (@guaranteed C) -> @out τ_0_0 + %7 = alloc_stack $Never + %8 = apply %6(%7, %5) : $@convention(thin) <τ_0_0> (@guaranteed C) -> @out τ_0_0 + unreachable +} From 52b87c25cf25eb8c12d861d7e09eb92b7849eb61 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Thu, 29 Sep 2022 13:10:57 -0700 Subject: [PATCH 11/19] Temporarily disable a SILVerifier unit test. We can't verify that store borrows aren't nested until we can reliably compute liveness. This can be fixed in two ways, both of which we plan to do ASAP: (1) With complete lifetimes, this no longer needs to perform transitive liveness at all. (2) findInnerTransitiveGuaranteedUses, which ends up being called on the load_borrow to compute liveness, can be taught to transitively process InteriorPointer uses instead of returning PointerEscape. We need to make sure all uses of the utility need to handle this first. --- lib/SIL/Verifier/SILVerifier.cpp | 12 +++++++++++- test/SIL/store_borrow_verify_errors.sil | 22 +++++++++++++--------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index cfb6c20dad099..e07687d29eedb 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -2446,7 +2446,17 @@ class SILVerifier : public SILVerifierBase { SSAPrunedLiveness scopedAddressLiveness; ScopedAddressValue scopedAddress(SI); - // FIXME: with complete lifetimes, this should not be a transitive check. + // FIXME: Reenable @test_load_borrow_store_borrow_nested in + // store_borrow_verify_errors once computeLivess can successfully handle a + // store_borrow within a load_borrow. This can be fixed in two ways + // + // (1) With complete lifetimes, this no longer needs to perform transitive + // liveness at all. + // + // (2) findInnerTransitiveGuaranteedUses, which ends up being called on the + // load_borrow to compute liveness, can be taught to transitively process + // InteriorPointer uses instead of returning PointerEscape. We need to make + // sure all uses of the utility need to handle this first. AddressUseKind useKind = scopedAddress.computeTransitiveLiveness(scopedAddressLiveness); bool success = useKind == AddressUseKind::NonEscaping; diff --git a/test/SIL/store_borrow_verify_errors.sil b/test/SIL/store_borrow_verify_errors.sil index bf75ca78d0bd6..2021ed77556a8 100644 --- a/test/SIL/store_borrow_verify_errors.sil +++ b/test/SIL/store_borrow_verify_errors.sil @@ -122,15 +122,19 @@ bb0(%0 : @guaranteed $Klass): return %1 : $() } -// CHECK: Begin Error in function test_load_borrow_store_borrow_nested -// CHECK: SIL verification failed: A store_borrow cannot be nested within another store_borrow to its destination -// CHECK: Verifying instruction: -// CHECK: %0 = argument of bb0 : $Klass // user: %2 -// CHECK: %1 = alloc_stack $Klass // users: %8, %4, %2 -// CHECK: -> %2 = store_borrow %0 to %1 : $*Klass // users: %7, %3 -// CHECK: %3 = load_borrow %2 : $*Klass // users: %6, %4 -// CHECK: end_borrow %2 : $*Klass // id: %7 -// CHECK: End Error in function test_load_borrow_store_borrow_nested +// FIXME: SILVerifier checkStoreBorrowInst needs to be fixed to +// successfully compute liveness of a load_borrow that contains a +// store_borrow. +// +// HECK: Begin Error in function test_load_borrow_store_borrow_nested +// HECK: SIL verification failed: A store_borrow cannot be nested within another store_borrow to its destination +// HECK: Verifying instruction: +// HECK: %0 = argument of bb0 : $Klass // user: %2 +// HECK: %1 = alloc_stack $Klass // users: %8, %4, %2 +// HECK: -> %2 = store_borrow %0 to %1 : $*Klass // users: %7, %3 +// HECK: %3 = load_borrow %2 : $*Klass // users: %6, %4 +// HECK: end_borrow %2 : $*Klass // id: %7 +// HECK: End Error in function test_load_borrow_store_borrow_nested sil [ossa] @test_load_borrow_store_borrow_nested : $@convention(thin) (@guaranteed Klass) -> () { bb0(%0 : @guaranteed $Klass): %stk = alloc_stack $Klass From d98ccb0e1cba501ff4743fb279215043e7141de4 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 3 Oct 2022 15:13:27 -0700 Subject: [PATCH 12/19] Fix a comment merge error --- include/swift/SIL/PrunedLiveness.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/swift/SIL/PrunedLiveness.h b/include/swift/SIL/PrunedLiveness.h index 125488f7b4829..294858bb96db3 100644 --- a/include/swift/SIL/PrunedLiveness.h +++ b/include/swift/SIL/PrunedLiveness.h @@ -668,11 +668,6 @@ class SSAPrunedLiveness : public PrunedLiveRange { /// Compute liveness for a single SSA definition. The lifetime-ending uses are /// also recorded--destroy_value or end_borrow. /// - /// This only handles simple liveness in which this definition reaches all - /// uses without involving phis. If the returned summary includes - /// InnerBorrowKind::Reborrow, then the potentially non-dominated uses are - /// not included. - /// /// This only handles simple liveness in which all uses are dominated by the /// definition. If the returned summary includes InnerBorrowKind::Reborrow, /// then the resulting liveness does not includes potentially non-dominated From 22448755d556ad48a56ab88edd0ba8742826ab38 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 3 Oct 2022 15:14:07 -0700 Subject: [PATCH 13/19] Add a clarifying comment to updateTransitiveLiveness --- include/swift/SIL/ScopedAddressUtils.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/swift/SIL/ScopedAddressUtils.h b/include/swift/SIL/ScopedAddressUtils.h index dacd8c6c19c26..b176eeae947f5 100644 --- a/include/swift/SIL/ScopedAddressUtils.h +++ b/include/swift/SIL/ScopedAddressUtils.h @@ -107,6 +107,9 @@ struct ScopedAddressValue { AddressUseKind computeTransitiveLiveness(SSAPrunedLiveness &liveness) const; /// Update \p liveness for all the transitive address uses. + /// + /// Valid for any type of liveness, SSA or MultiDef, that may be used by a + /// scoped address. AddressUseKind updateTransitiveLiveness(PrunedLiveness &liveness) const; /// Create appropriate scope ending instruction at \p insertPt. From 6346bf55c5a66d4b8d36f369b048dae4e4e805e6 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 3 Oct 2022 15:15:31 -0700 Subject: [PATCH 14/19] Rename areUsesWithinTransitiveScope to ...ExtendedScope Start using consistent terminolfy in ownership utils. A transitive use set follows transitive uses within an ownership lifetime. It does not rely on complete inner scopes. An extended use set is not necessarilly transitive but does look across lifetime-ending uses: copies of owned values and/or reborrows of guaranteed values. Whether lifetime extension refers to copies or reborrow is context dependent. --- include/swift/SIL/OwnershipUtils.h | 4 ++-- lib/SIL/Utils/OwnershipUtils.cpp | 4 ++-- lib/SILOptimizer/SemanticARC/CopyValueOpts.cpp | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/swift/SIL/OwnershipUtils.h b/include/swift/SIL/OwnershipUtils.h index aeaa67b5bd81f..ec091aa9473a0 100644 --- a/include/swift/SIL/OwnershipUtils.h +++ b/include/swift/SIL/OwnershipUtils.h @@ -582,8 +582,8 @@ struct BorrowedValue { /// /// \p deadEndBlocks is optional during transition. It will be completely /// removed in an upcoming commit. - bool areUsesWithinTransitiveScope(ArrayRef uses, - DeadEndBlocks *deadEndBlocks) const; + bool areUsesWithinExtendedScope(ArrayRef uses, + DeadEndBlocks *deadEndBlocks) const; /// Given a local borrow scope introducer, visit all non-forwarding consuming /// users. This means that this looks through guaranteed block arguments. \p diff --git a/lib/SIL/Utils/OwnershipUtils.cpp b/lib/SIL/Utils/OwnershipUtils.cpp index 9a71709a0c632..26b3ce722da19 100644 --- a/lib/SIL/Utils/OwnershipUtils.cpp +++ b/lib/SIL/Utils/OwnershipUtils.cpp @@ -769,7 +769,7 @@ computeTransitiveLiveness(MultiDefPrunedLiveness &liveness) const { }); } -bool BorrowedValue::areUsesWithinTransitiveScope( +bool BorrowedValue::areUsesWithinExtendedScope( ArrayRef uses, DeadEndBlocks *deadEndBlocks) const { // First make sure that we actually have a local scope. If we have a non-local // scope, then we have something (like a SILFunctionArgument) where a larger @@ -1087,7 +1087,7 @@ bool AddressOwnership::areUsesWithinLifetime( SILValue root = base.getOwnershipReferenceRoot(); BorrowedValue borrow(root); if (borrow) - return borrow.areUsesWithinTransitiveScope(uses, &deadEndBlocks); + return borrow.areUsesWithinExtendedTransitiveScope(uses, &deadEndBlocks); // --- A reference with no borrow scope! Currently happens for project_box. diff --git a/lib/SILOptimizer/SemanticARC/CopyValueOpts.cpp b/lib/SILOptimizer/SemanticARC/CopyValueOpts.cpp index 6b95186702eb2..1686d2ce6005d 100644 --- a/lib/SILOptimizer/SemanticARC/CopyValueOpts.cpp +++ b/lib/SILOptimizer/SemanticARC/CopyValueOpts.cpp @@ -155,7 +155,7 @@ bool SemanticARCOptVisitor::performGuaranteedCopyValueOptimization( // TODO: There may be some way of sinking this into the loop below. // // FIXME: The haveAnyLocalScopes and destroy.empty() checks are relics of - // attempts to handle dead end blocks during areUsesWithinTransitiveScope. If + // attempts to handle dead end blocks during areUsesWithinExtendedScope. If // we don't use dead end blocks at all, they should not be relevant. bool haveAnyLocalScopes = llvm::any_of(borrowScopeIntroducers, [](BorrowedValue borrowScope) { @@ -182,13 +182,13 @@ bool SemanticARCOptVisitor::performGuaranteedCopyValueOptimization( // need to insert an end_borrow since all of our borrow introducers are // non-local scopes. // - // The call to areUsesWithinTransitiveScope cannot consider dead-end blocks. A + // The call to areUsesWithinExtendedScope cannot consider dead-end blocks. A // local borrow scope requires all its inner uses to be inside the borrow // scope, regardless of whether the end of the scope is inside a dead-end // block. { if (llvm::any_of(borrowScopeIntroducers, [&](BorrowedValue borrowScope) { - return !borrowScope.areUsesWithinTransitiveScope( + return !borrowScope.areUsesWithinExtendedScope( lr.getAllConsumingUses(), nullptr); })) { return false; @@ -217,7 +217,7 @@ bool SemanticARCOptVisitor::performGuaranteedCopyValueOptimization( } if (llvm::any_of(borrowScopeIntroducers, [&](BorrowedValue borrowScope) { - return !borrowScope.areUsesWithinTransitiveScope( + return !borrowScope.areUsesWithinExtendedScope( phiArgLR.getAllConsumingUses(), nullptr); })) { return false; From 6861b318ca154f718008df1490485a2537f0da31 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 3 Oct 2022 16:01:16 -0700 Subject: [PATCH 15/19] Make PrunedLiveness::computeSimple one-level transitive Computing simple liveness is distinct from computing transitive liveness. But for safety and consistency, always handle the first level of liveness transitively. This way, computeSimple can be used on guaranteed values that need OSSA lifetime fixup. Simple liveness just means that *inner* borrow and address scopes are assumed to be complete. This utility is still only used conservatively because OSSA lifetime completion is not yet enabled. But, in the future, computeSimple can be used in the common case. --- include/swift/SIL/PrunedLiveness.h | 2 +- lib/SIL/Utils/OwnershipUtils.cpp | 2 +- lib/SIL/Utils/PrunedLiveness.cpp | 25 +++++++++++++++++++++---- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/include/swift/SIL/PrunedLiveness.h b/include/swift/SIL/PrunedLiveness.h index 294858bb96db3..c74358415476d 100644 --- a/include/swift/SIL/PrunedLiveness.h +++ b/include/swift/SIL/PrunedLiveness.h @@ -457,7 +457,7 @@ class PrunedLiveness { /// 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. - void checkAndUpdateInteriorPointer(Operand *operand); + AddressUseKind checkAndUpdateInteriorPointer(Operand *operand); /// Update this liveness to extend across the given liveness. void extendAcrossLiveness(PrunedLiveness &otherLiveness); diff --git a/lib/SIL/Utils/OwnershipUtils.cpp b/lib/SIL/Utils/OwnershipUtils.cpp index 26b3ce722da19..2fdc555ecfbf8 100644 --- a/lib/SIL/Utils/OwnershipUtils.cpp +++ b/lib/SIL/Utils/OwnershipUtils.cpp @@ -1087,7 +1087,7 @@ bool AddressOwnership::areUsesWithinLifetime( SILValue root = base.getOwnershipReferenceRoot(); BorrowedValue borrow(root); if (borrow) - return borrow.areUsesWithinExtendedTransitiveScope(uses, &deadEndBlocks); + return borrow.areUsesWithinExtendedScope(uses, &deadEndBlocks); // --- A reference with no borrow scope! Currently happens for project_box. diff --git a/lib/SIL/Utils/PrunedLiveness.cpp b/lib/SIL/Utils/PrunedLiveness.cpp index d8d518443ace4..b5288c079d08c 100644 --- a/lib/SIL/Utils/PrunedLiveness.cpp +++ b/lib/SIL/Utils/PrunedLiveness.cpp @@ -133,7 +133,7 @@ InnerBorrowKind PrunedLiveness::updateForBorrowingOperand(Operand *operand) { return InnerBorrowKind::Contained; } -void PrunedLiveness::checkAndUpdateInteriorPointer(Operand *operand) { +AddressUseKind PrunedLiveness::checkAndUpdateInteriorPointer(Operand *operand) { assert(operand->getOperandOwnership() == OperandOwnership::InteriorPointer); if (auto *svi = dyn_cast(operand->getUser())) { @@ -142,10 +142,17 @@ void PrunedLiveness::checkAndUpdateInteriorPointer(Operand *operand) { updateForUse(end->getUser(), /*lifetimeEnding*/ false); return true; }); - return; + return AddressUseKind::NonEscaping; } } - updateForUse(operand->getUser(), /*lifetimeEnding*/ false); + // FIXME: findTransitiveUses should be a visitor so we're not recursively + // allocating use vectors and potentially merging the use points. + SmallVector uses; + auto useKind = InteriorPointerOperand(operand).findTransitiveUses(&uses); + for (auto *use : uses) { + updateForUse(use->getUser(), /*lifetimeEnding*/ false); + } + return useKind; } void PrunedLiveness::extendAcrossLiveness(PrunedLiveness &otherLivesness) { @@ -276,8 +283,18 @@ SimpleLiveRangeSummary PrunedLiveRange::updateForDef(SILValue summary.meet(AddressUseKind::PointerEscape); break; case OperandOwnership::InteriorPointer: - checkAndUpdateInteriorPointer(use); + summary.meet(checkAndUpdateInteriorPointer(use)); break; + case OperandOwnership::ForwardingBorrow: { + ForwardingOperand(use).visitForwardedValues([&](SILValue result) { + // Do not include transitive uses with 'none' ownership + if (result->getOwnershipKind() != OwnershipKind::None) { + updateForDef(result); + } + return true; + }); + break; + } default: updateForUse(use->getUser(), use->isLifetimeEnding()); break; From 9e0b615593842016ab85f86a78a641eceef7f43c Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 3 Oct 2022 16:07:47 -0700 Subject: [PATCH 16/19] Add a comment on liveness terminology --- include/swift/SIL/OwnershipUtils.h | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/include/swift/SIL/OwnershipUtils.h b/include/swift/SIL/OwnershipUtils.h index ec091aa9473a0..16353143ddca0 100644 --- a/include/swift/SIL/OwnershipUtils.h +++ b/include/swift/SIL/OwnershipUtils.h @@ -9,6 +9,36 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +/// +/// Terminology: +/// +/// Simple liveness: +/// - Ends at lifetime-ending operations. +/// - Transitively follows guaranteed forwarding operations and address uses +/// within the current scope. +/// - Assumes inner scopes are complete, including borrow and address scopes +/// - Rarely returns AddressUseKind::PointerEscape. +/// - Per-definition dominance holds +/// - Insulates outer scopes from inner scope details. Maintains the +/// invariant that inlining cannot pessimize optimization. +/// +/// Transitive liveness +/// - Transitively follows uses within inner scopes, including forwarding +/// operations and address uses. +/// - Much more likely to returns AddressUseKind::PointerEscape +/// - Per-definition dominance holds +/// - Does not assume that any scopes are complete. +/// +/// Extended liveness (copy-extension and reborrow-extension) +/// - Extends a live range across lifetime-ending operations +/// - Depending on context: owned values are extended across copies or +/// guaranteed values are extended across reborrows +/// - Copy-extension is used to canonicalize an OSSA lifetime +/// - Reborrow-extension is used to check borrow scopes relative to its inner +/// uses and outer lifetime +/// - Per-definition dominance does not hold +/// +//===----------------------------------------------------------------------===// #ifndef SWIFT_SIL_OWNERSHIPUTILS_H #define SWIFT_SIL_OWNERSHIPUTILS_H From 98b6e09012340837fd4798b5de15e7147a6fe1b2 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 3 Oct 2022 16:33:59 -0700 Subject: [PATCH 17/19] Add a transitive liveness test case --- test/SILOptimizer/ossa_lifetime_analysis.sil | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/SILOptimizer/ossa_lifetime_analysis.sil b/test/SILOptimizer/ossa_lifetime_analysis.sil index ed7f68f2c8eb7..301e107d6d93e 100644 --- a/test/SILOptimizer/ossa_lifetime_analysis.sil +++ b/test/SILOptimizer/ossa_lifetime_analysis.sil @@ -20,6 +20,9 @@ case some(T) } class C {} +class D { + var object: C +} // CHECK-LABEL: @testSelfLoop // CHECK: SSA lifetime analysis: [[V:%.*]] = copy_value %0 : $C @@ -109,3 +112,23 @@ bb4: %99 = tuple() return %99 : $() } + +// CHECK-LABEL: @testGuaranteedForwarding +// CHECK: SSA lifetime analysis: [[C:%.*]] = unchecked_ref_cast %{{.*}} : $D to $C +// CHECK: bb0: LiveWithin +// CHECK: regular user: %{{.*}} = load [copy] +// CHECK: last user: %{{.*}} = load [copy] +sil [ossa] @testGuaranteedForwarding : $@convention(thin) (@owned D) -> () { +bb0(%0 : @owned $D): + %borrow0 = begin_borrow %0 : $D + %c = unchecked_ref_cast %borrow0 : $D to $C + debug_value [trace] %c : $C + %d = unchecked_ref_cast %c : $C to $D + %f = ref_element_addr %d : $D, #D.object + %o = load [copy] %f : $*C + end_borrow %borrow0 : $D + destroy_value %o : $C + destroy_value %0 : $D + %99 = tuple() + return %99 : $() +} From 8f34d9cc2fee0a5392e2e41a7a56358432977f68 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 3 Oct 2022 21:33:24 -0700 Subject: [PATCH 18/19] Add a comment regarding an optimizer rule for self-loops. --- lib/SILOptimizer/Transforms/SimplifyCFG.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/SILOptimizer/Transforms/SimplifyCFG.cpp b/lib/SILOptimizer/Transforms/SimplifyCFG.cpp index 0396779153896..c464de85c8998 100644 --- a/lib/SILOptimizer/Transforms/SimplifyCFG.cpp +++ b/lib/SILOptimizer/Transforms/SimplifyCFG.cpp @@ -9,6 +9,24 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +/// +/// Note: Unreachable blocks must always be eliminated before simplifying +/// useless phis. Otherwise self-loops will result in invalid SIL: +/// +/// bb1(%phi): +/// apply %use(%phi) +/// %def = apply %getValue() +/// br bb1(%def) +/// +/// When bb1 is unreachable, %phi will be removed as useless: +/// bb1: +/// apply %use(%def) +/// %def = apply %getValue() +/// br bb1(%def) +/// +/// This is considered invalid SIL because SIL has a special SSA dominance rule +/// that does not allow a use above a def in the same block. +//===----------------------------------------------------------------------===// #define DEBUG_TYPE "sil-simplify-cfg" From 755bce217dcc8616f6302f84bb99235edef05624 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 3 Oct 2022 22:44:38 -0700 Subject: [PATCH 19/19] Remove complexity in findBoundariesInBlock We can remove a ton of complexity by assuming that SSA uses never occur before a def in the same block. This will hold true as long as useless phis are only removed after all unreachable code is first removed. Then we don't have the self-loop problem. The SILVerifier already checks this invariant and I added an in-depth comment in SimplifyCFG. --- include/swift/SIL/PrunedLiveness.h | 57 ++------------ lib/SIL/Utils/PrunedLiveness.cpp | 117 +++++++++++++++-------------- 2 files changed, 68 insertions(+), 106 deletions(-) diff --git a/include/swift/SIL/PrunedLiveness.h b/include/swift/SIL/PrunedLiveness.h index c74358415476d..b9a75ab4a46a9 100644 --- a/include/swift/SIL/PrunedLiveness.h +++ b/include/swift/SIL/PrunedLiveness.h @@ -530,11 +530,6 @@ struct PrunedLivenessBoundary { /// /// bool isDefBlock(SILBasicBlock *block) const /// -/// SILArgument *getArgDef(SILBasicBlock *block) const -/// -/// SILInstruction *findPreviousDef(SILInstruction *searchPos, -/// SILInstruction *nextDef) -/// template class PrunedLiveRange : public PrunedLiveness { protected: @@ -587,10 +582,6 @@ class PrunedLiveRange : public PrunedLiveness { /// by DeadEndBlocks. void computeBoundary(PrunedLivenessBoundary &boundary, ArrayRef postDomBlocks) const; - -protected: - void findBoundariesInBlock(SILBasicBlock *block, bool isLiveOut, - PrunedLivenessBoundary &boundary) const; }; // Singly-defined liveness. @@ -642,28 +633,9 @@ class SSAPrunedLiveness : public PrunedLiveRange { return def->getParentBlock() == block; } - /// If the argument list of \p block contains a definition, return it. - SILArgument *getArgDef(SILBasicBlock *block) const { - if (auto *arg = dyn_cast(def)) { - if (arg->getParent() == block) - return arg; - } - return nullptr; - } - - /// Return the definition if it occurs in the same block before \p searchPos. - /// - /// Precondition: if the definition occurs in the same block on or after \p - /// searchPos, then \p nextDef must point to the definition. - SILInstruction *findPreviousDef(SILInstruction *searchPos, - SILInstruction *nextDef) const { - if (!defInst || nextDef) { - assert(nextDef == defInst); - return nullptr; - } - auto *block = searchPos->getParent(); - return defInst->getParent() == block ? defInst : nullptr; - } + /// SSA implementation of computeBoundary. + void findBoundariesInBlock(SILBasicBlock *block, bool isLiveOut, + PrunedLivenessBoundary &boundary) const; /// Compute liveness for a single SSA definition. The lifetime-ending uses are /// also recorded--destroy_value or end_borrow. @@ -722,26 +694,9 @@ class MultiDefPrunedLiveness : public PrunedLiveRange { return defBlocks.contains(block); } - /// If the argument list of \p block contains a definition, return it. - SILArgument *getArgDef(SILBasicBlock *block) const { - if (!isDefBlock(block)) - return nullptr; - - for (SILArgument *arg : block->getArguments()) { - if (defs.contains(arg)) - return arg; - } - return nullptr; - } - - /// Return the previous definition that occurs in the same block before \p - /// searchPos, or nullptr if none exists. - /// - /// Precondition: if a definition occurs in the same block on or after \p - /// searchPos, then \p nextDef must point to the next definition. searchPos - /// cannot point to nextDef. - SILInstruction *findPreviousDef(SILInstruction *searchPos, - SILInstruction *nextDef) const; + /// Multi-Def implementation of computeBoundary. + void findBoundariesInBlock(SILBasicBlock *block, bool isLiveOut, + PrunedLivenessBoundary &boundary) const; /// Compute liveness for a all currently initialized definitions. The /// lifetime-ending uses are also recorded--destroy_value or diff --git a/lib/SIL/Utils/PrunedLiveness.cpp b/lib/SIL/Utils/PrunedLiveness.cpp index b5288c079d08c..a43a5cba5fc3c 100644 --- a/lib/SIL/Utils/PrunedLiveness.cpp +++ b/lib/SIL/Utils/PrunedLiveness.cpp @@ -367,44 +367,6 @@ bool PrunedLiveRange::areUsesOutsideBoundary( return true; } -template -void PrunedLiveRange::findBoundariesInBlock( - SILBasicBlock *block, bool isLiveOut, - PrunedLivenessBoundary &boundary) const { - assert(asImpl().isInitialized()); - - bool isLive = isLiveOut; - bool isDefBlock = asImpl().isDefBlock(block); - SILInstruction *nextDef = nullptr; - SILInstruction *searchPos = block->getTerminator(); - while (searchPos) { - if (isLive) { - nextDef = asImpl().findPreviousDef(searchPos, nextDef); - if (!nextDef) - return; - - searchPos = nextDef; - isLive = false; - } else if (isDefBlock) { - // Check if the previous instruction is a def before checking whether it - // is a use. The same instruction can be both a dead def and boundary use. - if (asImpl().isDef(searchPos)) { - boundary.deadDefs.push_back(cast(searchPos)); - } - } - if (isInterestingUser(searchPos)) { - boundary.lastUsers.push_back(searchPos); - isLive = true; - } - searchPos = searchPos->getPreviousInstruction(); - } - if (!isLive && isDefBlock) { - if (SILArgument *deadArg = asImpl().getArgDef(block)) { - boundary.deadDefs.push_back(deadArg); - } - } -} - template void PrunedLiveRange::computeBoundary( PrunedLivenessBoundary &boundary) const { @@ -419,10 +381,10 @@ void PrunedLiveRange::computeBoundary( boundary.boundaryEdges.push_back(succBB); } } - findBoundariesInBlock(block, /*isLiveOut*/ true, boundary); + asImpl().findBoundariesInBlock(block, /*isLiveOut*/ true, boundary); break; case PrunedLiveBlocks::LiveWithin: { - findBoundariesInBlock(block, /*isLiveOut*/ false, boundary); + asImpl().findBoundariesInBlock(block, /*isLiveOut*/ false, boundary); break; } case PrunedLiveBlocks::Dead: @@ -451,10 +413,10 @@ void PrunedLiveRange::computeBoundary( // Process each block that has not been visited and is not LiveOut. switch (getBlockLiveness(block)) { case PrunedLiveBlocks::LiveOut: - findBoundariesInBlock(block, /*isLiveOut*/ true, boundary); + asImpl().findBoundariesInBlock(block, /*isLiveOut*/ true, boundary); break; case PrunedLiveBlocks::LiveWithin: { - findBoundariesInBlock(block, /*isLiveOut*/ false, boundary); + asImpl().findBoundariesInBlock(block, /*isLiveOut*/ false, boundary); break; } case PrunedLiveBlocks::Dead: @@ -476,26 +438,71 @@ template class PrunedLiveRange; template class PrunedLiveRange; } // namespace swift +//===----------------------------------------------------------------------===// +// SSAPrunedLiveness +//===----------------------------------------------------------------------===// + +void SSAPrunedLiveness::findBoundariesInBlock( + SILBasicBlock *block, bool isLiveOut, + PrunedLivenessBoundary &boundary) const { + assert(isInitialized()); + + // For SSA, a live-out block cannot have a boundary. + if (isLiveOut) + return; + + bool isDefBlockState = isDefBlock(block); + for (SILInstruction &inst : llvm::reverse(*block)) { + if (isDefBlockState && isDef(&inst)) { + boundary.deadDefs.push_back(cast(&inst)); + return; + } + if (isInterestingUser(&inst)) { + boundary.lastUsers.push_back(&inst); + return; + } + } + auto *deadArg = dyn_cast(def); + assert(deadArg && deadArg->getParent() == block + && "findBoundariesInBlock must be called on a live block"); + boundary.deadDefs.push_back(deadArg); +} + //===----------------------------------------------------------------------===// // MultiDefPrunedLiveness //===----------------------------------------------------------------------===// -SILInstruction * -MultiDefPrunedLiveness::findPreviousDef(SILInstruction *searchPos, - SILInstruction *nextDef) const { - auto *block = searchPos->getParent(); - if (!defBlocks.contains(block)) - return nullptr; +void MultiDefPrunedLiveness::findBoundariesInBlock( + SILBasicBlock *block, bool isLiveOut, + PrunedLivenessBoundary &boundary) const { + assert(isInitialized()); + unsigned prevCount = boundary.deadDefs.size() + boundary.lastUsers.size(); - auto it = searchPos->getReverseIterator(); - if (searchPos == nextDef) { - ++it; + bool isLive = isLiveOut; + bool isDefBlockState = isDefBlock(block); + for (auto &inst : llvm::reverse(*block)) { + // Check if the instruction is a def before checking whether it is a + // use. The same instruction can be both a dead def and boundary use. + if (isDefBlockState && isDef(&inst)) { + if (!isLive) { + boundary.deadDefs.push_back(cast(&inst)); + } + isLive = false; + } + if (!isLive && isInterestingUser(&inst)) { + boundary.lastUsers.push_back(&inst); + isLive = true; + } } - for (auto end = block->rend(); it != end; ++it) { - if (isDef(&*it)) - return &*it; + if (!isLive && isDefBlockState) { + for (SILArgument *deadArg : block->getArguments()) { + if (defs.contains(deadArg)) { + boundary.deadDefs.push_back(deadArg); + } + } } - return nullptr; + assert(prevCount < boundary.deadDefs.size() + boundary.lastUsers.size() + && "findBoundariesInBlock must be called on a live block"); } SimpleLiveRangeSummary MultiDefPrunedLiveness::computeSimple() {