//===-- OSSACanonicalizeOwned.cpp - Canonicalize OSSA value lifetimes --===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// /// /// This top-level API rewrites the extended lifetime of a SILValue: /// /// bool OSSACanonicalizeOwned::canonicalizeValueLifetime(SILValue def) /// /// Each time it's called on a single OSSA value, `def`, it performs four /// steps: /// /// 1. Compute "pruned" liveness of def and its copies, ignoring original /// destroys. Initializes `liveness`. /// /// 2. Find the "original" boundary of liveness using /// PrunedLiveness::computeBoundary. /// /// 3. (Optional) At Onone, extend liveness up to original extent when possible /// without incurring extra copies. /// /// 4. Find the "extended" boundary of liveness by walking out from the boundary /// computed by PrunedLiveness out to destroys which aren't separated from /// the original destory by "interesting" instructions. /// /// 5. Initializes `consumes` and inserts new destroy_value instructions. /// /// 6. Rewrite `def`s original copies and destroys, inserting new copies where /// needed. Deletes original copies and destroys and inserts new copies. /// /// See OSSACanonicalizeOwned.h for examples. /// /// TODO: Canonicalization currently bails out if any uses of the def has /// OperandOwnership::PointerEscape. Once project_box is protected by a borrow /// scope and mark_dependence is associated with an end_dependence, those will /// no longer be represented as PointerEscapes, and canonicalization will /// naturally work everywhere as intended. The intention is to keep the /// canonicalization algorithm as simple and robust, leaving the remaining /// performance opportunities contingent on fixing the SIL representation. /// /// TODO: Replace BasicBlock SmallDenseMaps with inlined bits; /// see BasicBlockDataStructures.h. /// /// TODO: This algorithm would be extraordinarily simple and cheap except for /// the following issues: /// /// 1. Liveness is extended by any overlapping begin/end_access scopes. This /// avoids calling a destructor within an exclusive access. A simpler /// alternative would be to model all end_access instructions as deinit /// barriers, but that may significantly limit optimization. /// /// 2. Liveness is extended out to original destroys to avoid spurious changes. /// /// 3. In the Onone mode, liveness is preserved to its previous extent whenever /// doing so doesn't incur extra copies compared to what is done in the O mode. /// //===----------------------------------------------------------------------===// #define DEBUG_TYPE "copy-propagation" #include "swift/SILOptimizer/Utils/OSSACanonicalizeOwned.h" #include "swift/Basic/Assertions.h" #include "swift/SIL/InstructionUtils.h" #include "swift/SIL/NodeDatastructures.h" #include "swift/SIL/OSSACompleteLifetime.h" #include "swift/SIL/OwnershipUtils.h" #include "swift/SIL/PrunedLiveness.h" #include "swift/SIL/Test.h" #include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h" #include "swift/SILOptimizer/Analysis/Reachability.h" #include "swift/SILOptimizer/PassManager/Transforms.h" #include "swift/SILOptimizer/Utils/CFGOptUtils.h" #include "swift/SILOptimizer/Utils/DebugOptUtils.h" #include "swift/SILOptimizer/Utils/InstructionDeleter.h" #include "swift/SILOptimizer/Utils/ValueLifetime.h" #include "llvm/ADT/Statistic.h" using namespace swift; using llvm::SmallSetVector; llvm::Statistic swift::NumCopiesAndMovesEliminated = { DEBUG_TYPE, "NumCopiesAndMovesEliminated", "number of copy_value and move_value instructions removed"}; llvm::Statistic swift::NumCopiesGenerated = { DEBUG_TYPE, "NumCopiesGenerated", "number of copy_value instructions created"}; STATISTIC(NumDestroysEliminated, "number of destroy_value instructions removed"); STATISTIC(NumDestroysGenerated, "number of destroy_value instructions created"); //===----------------------------------------------------------------------===// // MARK: General utilities //===----------------------------------------------------------------------===// template static void diagnose(ASTContext &Context, SourceLoc loc, Diag diag, U &&...args) { Context.Diags.diagnose(loc, diag, std::forward(args)...); } /// Is \p instruction a destroy_value whose operand is \p def, or its /// transitive copy. static bool isDestroyOfCopyOf(SILInstruction *instruction, SILValue def) { auto *destroy = dyn_cast(instruction); if (!destroy) return false; auto destroyed = destroy->getOperand(); while (true) { if (destroyed == def) return true; auto *copy = dyn_cast(destroyed); if (!copy) break; destroyed = copy->getOperand(); } return false; } //===----------------------------------------------------------------------===// // MARK: Step 1. Compute pruned liveness //===----------------------------------------------------------------------===// bool OSSACanonicalizeOwned::computeCanonicalLiveness() { LLVM_DEBUG(llvm::dbgs() << "Computing canonical liveness from:\n"; getCurrentDef()->print(llvm::dbgs())); SmallVector indexWorklist; ValueSet visitedDefs(getCurrentDef()->getFunction()); auto addDefToWorklist = [&](Def def) { if (!visitedDefs.insert(def.value)) return; discoveredDefs.push_back(def); indexWorklist.push_back(discoveredDefs.size() - 1); }; discoveredDefs.clear(); addDefToWorklist(Def::root(getCurrentDef())); // Only the first level of reborrows need to be consider. All nested inner // adjacent reborrows and phis are encapsulated within their lifetimes. SILPhiArgument *arg; if ((arg = dyn_cast(getCurrentDef())) && arg->isPhi()) { visitInnerAdjacentPhis(arg, [&](SILArgument *reborrow) { addDefToWorklist(Def::reborrow(reborrow)); return true; }); } while (!indexWorklist.empty()) { auto index = indexWorklist.pop_back_val(); auto def = discoveredDefs[index]; auto value = def.value; LLVM_DEBUG(llvm::dbgs() << " Uses of value:\n"; value->print(llvm::dbgs())); for (Operand *use : value->getUses()) { LLVM_DEBUG(llvm::dbgs() << " Use:\n"; use->getUser()->print(llvm::dbgs())); auto *user = use->getUser(); // Recurse through copies. if (auto *copy = dyn_cast(user)) { // Don't recurse through copies of borrowed-froms or reborrows. switch (def.kind) { case Def::Root: case Def::Copy: addDefToWorklist(Def::copy(copy)); break; case Def::Reborrow: case Def::BorrowedFrom: break; } continue; } if (auto *bfi = dyn_cast(user)) { addDefToWorklist(Def::borrowedFrom(bfi)); continue; } // Handle debug_value instructions separately. if (pruneDebugMode) { if (auto *dvi = dyn_cast(user)) { // Only instructions potentially outside current pruned liveness are // interesting. if (liveness->getBlockLiveness(dvi->getParent()) != PrunedLiveBlocks::LiveOut) { recordDebugValue(dvi); } continue; } } switch (use->getOperandOwnership()) { case OperandOwnership::NonUse: break; case OperandOwnership::TrivialUse: llvm_unreachable("this operand cannot handle ownership"); // Conservatively treat a conversion to an unowned value as a pointer // escape. Is it legal to canonicalize ForwardingUnowned? case OperandOwnership::ForwardingUnowned: case OperandOwnership::PointerEscape: LLVM_DEBUG(llvm::dbgs() << " Value escaped! Giving up\n"); return false; case OperandOwnership::InstantaneousUse: case OperandOwnership::UnownedInstantaneousUse: case OperandOwnership::BitwiseEscape: liveness->updateForUse(user, /*lifetimeEnding*/ false); break; case OperandOwnership::ForwardingConsume: recordConsumingUse(use); liveness->updateForUse(user, /*lifetimeEnding*/ true); break; case OperandOwnership::DestroyingConsume: if (isDestroyOfCopyOf(user, getCurrentDef())) { destroys.insert(user); } else { // destroy_value of a transitive copy of the currentDef does not // force pruned liveness (but store etc. does). // Even though this instruction is a DestroyingConsume of its operand, // if it's a destroy_value whose operand is not a transitive copy of // currentDef, then it's just ending an implicit borrow of currentDef, // not consuming it. auto lifetimeEnding = !isa(user); liveness->updateForUse(user, lifetimeEnding); } recordConsumingUse(use); break; case OperandOwnership::Borrow: if (liveness->updateForBorrowingOperand(use) != InnerBorrowKind::Contained) { LLVM_DEBUG(llvm::dbgs() << " Inner borrow can't be contained! Giving up\n"); return false; } break; case OperandOwnership::InteriorPointer: case OperandOwnership::AnyInteriorPointer: if (liveness->checkAndUpdateInteriorPointer(use) != AddressUseKind::NonEscaping) { LLVM_DEBUG(llvm::dbgs() << " Inner address use is escaping! Giving up\n"); return false; } break; case OperandOwnership::GuaranteedForwarding: case OperandOwnership::EndBorrow: // Guaranteed values are exposed by inner adjacent reborrows. If user is // a guaranteed phi (GuaranteedForwarding), then the owned lifetime // either dominates it or its lifetime ends at an outer adjacent // reborrow. Only instructions that end the reborrow lifetime should // actually affect liveness of the outer owned value. liveness->updateForUse(user, /*lifetimeEnding*/ false); break; case OperandOwnership::Reborrow: BranchInst *branch = cast(user); // This is a cheap variation on visitEnclosingDef. We already know that // getCurrentDef() is the enclosing def for this use. If the reborrow's // has a enclosing def is an outer adjacent phi then this branch must // consume getCurrentDef() as the outer phi operand. if (is_contained(branch->getOperandValues(), getCurrentDef())) { // An adjacent phi consumes the value being reborrowed. Although this // use doesn't end the lifetime, this branch does end the lifetime by // consuming the owned value. liveness->updateForUse(branch, /*lifetimeEnding*/ true); break; } // No adjacent phi consumes the value. This use is not lifetime ending. liveness->updateForUse(branch, /*lifetimeEnding*/ false); // This branch reborrows a guaranteed phi whose lifetime is dependent on // currentDef. Uses of the reborrowing phi extend liveness. auto *reborrow = PhiOperand(use).getValue(); addDefToWorklist(Def::reborrow(reborrow)); break; } } } return true; } /// Extend liveness to the availability boundary of currentDef. Even if a copy /// is consumed on a path to the dead-end, if the def stays live through to the /// dead-end, its lifetime must not be shrunk back from it (eventually we'll /// support shrinking it back to deinit barriers). /// /// Example: /// %def is lexical /// %copy = copy_value %def /// consume %copy /// apply %foo() // deinit barrier /// // Must extend lifetime of %def up to this point per language rules. /// unreachable void OSSACanonicalizeOwned::extendLexicalLivenessToDeadEnds() { // TODO: OSSACompleteLifetime: Once lifetimes are always complete, delete // this method. SmallVector directDiscoverdBlocks; SSAPrunedLiveness directLiveness(function, &directDiscoverdBlocks); directLiveness.initializeDef(getCurrentDef()); directLiveness.computeSimple(); OSSACompleteLifetime::visitAvailabilityBoundary( getCurrentDef(), directLiveness, [&](auto *unreachable, auto end) { if (end == OSSACompleteLifetime::LifetimeEnd::Boundary) { recordUnreachableLifetimeEnd(unreachable); } unreachable->visitPriorInstructions([&](auto *inst) { liveness->extendToNonUse(inst); return true; }); }); } /// Extend liveness to the copy-extended availability boundary of currentDef. /// Prevents destroys from being inserted between borrows of (copies of) the /// def and dead-ends. /// /// Example: /// %def need not be lexical /// %c = copy_value %def /// %sb = store_borrow %c to %addr /// // Must extend lifetime of %def up to this point. Otherwise, a /// // destroy_value could be inserted within a borrow scope or interior /// // pointer use. /// unreachable void OSSACanonicalizeOwned::extendLivenessToDeadEnds() { // TODO: OSSACompleteLifetime: Once lifetimes are always complete, delete // this method. SmallVector discoveredBlocks(this->discoveredBlocks); SSAPrunedLiveness completeLiveness(*liveness, &discoveredBlocks); for (auto destroy : destroys) { if (liveness->isWithinBoundary(destroy, /*deadEndBlocks=*/nullptr)) continue; completeLiveness.updateForUse(destroy, /*lifetimeEnding*/ true); } // Demote consuming uses within complete liveness to non-consuming uses. // // OSSACompleteLifetime considers the lifetime of a single value. Such // lifetimes never continue beyond consumes. std::optional> lastUsers; auto isConsumeOnBoundary = [&](SILInstruction *instruction) -> bool { if (!lastUsers) { // Avoid computing lastUsers if possible. auto *function = getCurrentDef()->getFunction(); auto *deadEnds = deadEndBlocksAnalysis->get(function); llvm::SmallVector completeConsumingBlocks( consumingBlocks.getArrayRef()); for (auto &block : *function) { if (!deadEnds->isDeadEnd(&block)) continue; completeConsumingBlocks.push_back(&block); } PrunedLivenessBoundary boundary; liveness->computeBoundary(boundary, completeConsumingBlocks); lastUsers.emplace(); for (auto *lastUser : boundary.lastUsers) { lastUsers->insert(lastUser); } } return lastUsers->contains(instruction); }; for (auto pair : liveness->getAllUsers()) { if (!pair.second.isEnding()) continue; auto *instruction = pair.first; if (isConsumeOnBoundary(instruction)) continue; // Demote instruction's lifetime-ending-ness to non-lifetime-ending. completeLiveness.updateForUse(pair.first, /*lifetimeEnding=*/false); } OSSACompleteLifetime::visitAvailabilityBoundary( getCurrentDef(), completeLiveness, [&](auto *unreachable, auto end) { if (end == OSSACompleteLifetime::LifetimeEnd::Boundary) { recordUnreachableLifetimeEnd(unreachable); } unreachable->visitPriorInstructions([&](auto *inst) { liveness->extendToNonUse(inst); return true; }); }); } void OSSACanonicalizeOwned::extendLivenessToDeinitBarriers() { SmallVector ends; if (endingLifetimeAtExplicitEnds()) { visitExtendedUnconsumedBoundary( explicitLifetimeEnds, [&ends](auto *instruction, auto lifetimeEnding) { instruction->visitSubsequentInstructions([&](auto *next) { ends.push_back(next); return true; }); }); } else { for (auto destroy : destroys) { if (destroy->getOperand(0) != getCurrentDef()) continue; ends.push_back(destroy); } } auto *def = getCurrentDef()->getDefiningInstruction(); using InitialBlocks = ArrayRef; auto *defBlock = getCurrentDef()->getParentBlock(); auto initialBlocks = defBlock ? InitialBlocks(defBlock) : InitialBlocks(); ReachableBarriers barriers; findBarriersBackward( ends, initialBlocks, *getCurrentDef()->getFunction(), barriers, [&](auto *inst) { if (inst == def) return true; if (!isDeinitBarrier(inst, calleeAnalysis)) return false; // For the most part, instructions that are deinit // barriers in the abstract are also deinit barriers // for the purposes of canonicalizing def's lifetime. // // There is an important exception: transferring an // owned lexical lifetime into a callee. If the // instruction is a full apply which consumes def, // then it isn't a deinit barrier. Keep looking for // barriers above it. auto apply = FullApplySite::isa(inst); if (!apply) return true; return liveness->isInterestingUser(inst) != PrunedLiveness::IsInterestingUser::LifetimeEndingUse; }); for (auto *barrier : barriers.instructions) { liveness->extendToNonUse(barrier); } for (auto *barrier : barriers.phis) { for (auto *predecessor : barrier->getPredecessorBlocks()) { liveness->extendToNonUse(predecessor->getTerminator()); } } for (auto *edge : barriers.edges) { auto *predecessor = edge->getSinglePredecessorBlock(); assert(predecessor); liveness->extendToNonUse(&predecessor->back()); } // Ignore barriers.initialBlocks. If the collection is non-empty, it // contains the def-block. Its presence means that no barriers were found // between lifetime ends and def. In that case, no new instructions need to // be added to liveness. } // Return true if \p inst is an end_access whose access scope overlaps the end // of the pruned live range. This means that a hoisted destroy might execute // within the access scope which previously executed outside the access scope. // // Not overlapping (ignored): // // %def // use %def // pruned liveness ends here // begin_access // access scope unrelated to def // end_access // // Overlapping (must extend pruned liveness): // // %def // begin_access // access scope unrelated to def // use %def // pruned liveness ends here // end_access // // Overlapping (must extend pruned liveness): // // begin_access // access scope unrelated to def // %def // use %def // pruned liveness ends here // end_access // bool OSSACanonicalizeOwned::endsAccessOverlappingPrunedBoundary( SILInstruction *inst) { if (isa(inst)) { return true; } auto *endAccess = dyn_cast(inst); if (!endAccess) { return false; } auto *beginAccess = endAccess->getBeginAccess(); SILBasicBlock *beginBB = beginAccess->getParent(); switch (liveness->getBlockLiveness(beginBB)) { case PrunedLiveBlocks::LiveOut: // Found partial overlap of the form: // currentDef // beginAccess // br... // bb... // use // endAccess return true; case PrunedLiveBlocks::LiveWithin: // Check for partial overlap of this form where beginAccess and the last use // are in the same block: // currentDef // beginAccess // use // endAccess if (std::find_if(std::next(beginAccess->getIterator()), beginBB->end(), [this](SILInstruction &nextInst) { return liveness->isInterestingUser(&nextInst) != PrunedLiveness::NonUser; }) != beginBB->end()) { // An interesting use after the beginAccess means overlap. return true; } return false; case PrunedLiveBlocks::Dead: // Check for partial overlap of this form where beginAccess and currentDef // are in different blocks: // beginAccess // br... // bb... // currentDef // endAccess // // Since beginAccess is not within the canonical live range, its access // scope overlaps only if there is a path from beginAccess to currentDef // that does not pass through endAccess. endAccess is dominated by // both currentDef and begin_access. Therefore, such a path only exists if // beginAccess dominates currentDef. return domTree->properlyDominates(beginAccess->getParent(), getCurrentDef()->getParentBlock()); } llvm_unreachable("covered switch"); } // Find all overlapping access scopes and extend pruned liveness to cover them: // // This may also unnecessarily, but conservatively extend liveness over some // originally overlapping access, such as: // // begin_access // access scope unrelated to def // %def // use %def // destroy %def // end_access // // Or: // // %def // begin_access // access scope unrelated to def // use %def // destroy %def // end_access // // To minimize unnecessary lifetime extension, only search for end_access // within dead blocks that are backward reachable from an original destroy. // // Note that lifetime extension is iterative because adding a new liveness use // may create new overlapping access scopes. This can happen because there is no // guarantee of strict stack discipline across unrelated access. For example: // // %def // begin_access A // use %def // Initial pruned lifetime boundary // begin_access B // end_access A // Lifetime boundary after first extension // end_access B // Lifetime boundary after second extension // destroy %def // // If the lifetime extension did not iterate, then def would be destroyed within // B's access scope when originally it was destroyed outside that scope. void OSSACanonicalizeOwned::extendLivenessThroughOverlappingAccess() { this->accessBlocks = accessBlockAnalysis->get(getCurrentDef()->getFunction()); // Visit each original consuming use or destroy as the starting point for a // backward CFG traversal. This traversal must only visit blocks within the // original extended lifetime. bool changed = true; while (changed) { changed = false; // The blocks in which we may have to extend liveness over access scopes. // // It must be populated first so that we can test membership during the loop // (see findLastConsume). BasicBlockSetVector blocksToVisit(getCurrentDef()->getFunction()); for (auto *block : consumingBlocks) { blocksToVisit.insert(block); } for (auto iterator = blocksToVisit.begin(); iterator != blocksToVisit.end(); ++iterator) { auto *bb = *iterator; // If the block isn't dead, then we won't need to extend liveness within // any of its predecessors (though we may within it). if (liveness->getBlockLiveness(bb) != PrunedLiveBlocks::Dead) continue; // Continue searching upward to find the pruned liveness boundary. for (auto *predBB : bb->getPredecessorBlocks()) { blocksToVisit.insert(predBB); } } for (auto *bb : blocksToVisit) { auto blockLiveness = liveness->getBlockLiveness(bb); // Ignore blocks within pruned liveness. if (blockLiveness == PrunedLiveBlocks::LiveOut) { continue; } if (blockLiveness == PrunedLiveBlocks::Dead) { // Otherwise, ignore dead blocks with no nonlocal end_access. if (!accessBlocks->containsNonLocalEndAccess(bb)) { continue; } } bool blockHasUse = (blockLiveness == PrunedLiveBlocks::LiveWithin); // Find the latest partially overlapping access scope, if one exists: // use %def // pruned liveness ends here // end_access // Whether to look for the last consume in the block. // // We need to avoid extending liveness over end_accesses that occur after // original liveness ended. bool findLastConsume = consumingBlocks.contains(bb) && llvm::none_of(bb->getSuccessorBlocks(), [&](auto *successor) { return blocksToVisit.contains(successor) && liveness->getBlockLiveness(successor) == PrunedLiveBlocks::Dead; }); for (auto &inst : llvm::reverse(*bb)) { if (findLastConsume) { findLastConsume = !destroys.contains(&inst); continue; } // Stop at the latest use. An earlier end_access does not overlap. if (blockHasUse && liveness->isInterestingUser(&inst) != PrunedLiveness::NonUser) { break; } if (endsAccessOverlappingPrunedBoundary(&inst)) { liveness->extendToNonUse(&inst); changed = true; break; } } // If liveness changed, might as well restart CFG traversal. if (changed) { break; } } } } //===----------------------------------------------------------------------===// // MARK: Step 2. Find the "original" (unextended) boundary determined by the // liveness built up in step 1. //===----------------------------------------------------------------------===// void OSSACanonicalizeOwned::findOriginalBoundary( PrunedLivenessBoundary &boundary) { assert(boundary.lastUsers.size() == 0 && boundary.boundaryEdges.size() == 0 && boundary.deadDefs.size() == 0); liveness->computeBoundary(boundary, consumingBlocks.getArrayRef()); } //===----------------------------------------------------------------------===// // MARK: Step 3. (Optional) Maximize lifetimes. //===----------------------------------------------------------------------===// /// At -Onone, there are some conflicting goals: /// On the one hand: good debugging experience. /// (1) do not shorten value's lifetime /// On the other: demonstrate semantics. /// (2) consume value at same places it will be consumed at -O /// (3) ensure there are no more copies than there would be at -O /// /// (2) and (3) are equivalent--extra (compared to -O) copies arise from failing /// to end lifetimes at consuming uses (which then need their own copies). /// /// We achieve (2) and (3) always. We achieve (1) where possible. /// /// Conceptually, the strategy is the following: /// - Collect the blocks in which the value was live before canonicalization. /// These are the "original" live blocks (originalLiveBlocks). /// [Color these blocks green.] /// - From within that collection, collect the blocks which contain a _final_ /// consuming, non-destroy use, and their iterative successors. /// These are the "consumed" blocks (consumedAtExitBlocks). /// [Color these blocks red.] /// - Extend liveness down to the boundary between originalLiveBlocks and /// consumedAtExitBlocks blocks. /// [Extend liveness down to the boundary between green blocks and red.] /// - In particular, in regions of originalLiveBlocks which have no boundary /// with consumedAtExitBlocks, liveness should be extended to its original /// extent. /// [Extend liveness down to the boundary between green blocks and uncolored.] void OSSACanonicalizeOwned::visitExtendedUnconsumedBoundary( ArrayRef consumes, llvm::function_ref visitor) { auto currentDef = getCurrentDef(); #ifndef NDEBUG for (auto *consume : consumes) { assert(!liveness->isWithinBoundary(consume, /*deadEndBlocks=*/nullptr)); } #endif // First, collect the blocks that were _originally_ live. We can't use // liveness here because it doesn't include blocks that occur before a // destroy_value. BasicBlockSet originalLiveBlocks(currentDef->getFunction()); { // Some of the work here was already done by computeCanonicalLiveness. // Specifically, it already discovered all blocks containing (transitive) // uses and blocks that appear between them and the def. // // Seed the set with what it already discovered. for (auto *discoveredBlock : liveness->getDiscoveredBlocks()) originalLiveBlocks.insert(discoveredBlock); // Start the walk from the consuming blocks (which includes destroys as well // as the other consuming uses). BasicBlockWorklist worklist(currentDef->getFunction()); for (auto *consumingBlock : consumingBlocks) { worklist.push(consumingBlock); } // Walk backwards from consuming blocks. while (auto *block = worklist.pop()) { if (!originalLiveBlocks.insert(block)) continue; for (auto *predecessor : block->getPredecessorBlocks()) { // If the block was discovered by liveness, we already added it to the // set. if (originalLiveBlocks.contains(predecessor)) continue; worklist.pushIfNotVisited(predecessor); } } } // Second, collect the blocks which contain a _final_ consuming use and their // iterative successors within the originalLiveBlocks. BasicBlockSet consumedAtExitBlocks(currentDef->getFunction()); // The subset of consumedAtExitBlocks which do not contain a _final_ consuming // use, i.e. the subset that is dead. StackList consumedAtEntryBlocks(currentDef->getFunction()); { // Start the forward walk from blocks which contain _final_ non-destroy // consumes. These are just the instructions on the boundary which aren't // destroys. BasicBlockWorklist worklist(currentDef->getFunction()); for (auto *instruction : consumes) { if (destroys.contains(instruction)) continue; if (liveness->isInterestingUser(instruction) != PrunedLiveness::IsInterestingUser::LifetimeEndingUse) continue; worklist.push(instruction->getParent()); } while (auto *block = worklist.pop()) { consumedAtExitBlocks.insert(block); for (auto *successor : block->getSuccessorBlocks()) { if (!originalLiveBlocks.contains(successor)) continue; worklist.pushIfNotVisited(successor); consumedAtEntryBlocks.push_back(successor); } } } // Third, find the blocks on the boundary between the originalLiveBlocks // blocks and the consumedAtEntryBlocks blocks. Extend liveness "to the end" // of these blocks. for (auto *block : consumedAtEntryBlocks) { for (auto *predecessor : block->getPredecessorBlocks()) { if (consumedAtExitBlocks.contains(predecessor)) continue; // Add "the instruction(s) before the terminator" of the predecessor to // liveness. predecessor->getTerminator()->visitPriorInstructions([&](auto *inst) { visitor(inst, PrunedLiveness::LifetimeEnding::Value::NonUse); return true; }); } } // Finally, preserve the destroys which weren't in the consumed region in // place: hoisting such destroys would not avoid copies. for (auto *destroy : destroys) { auto *block = destroy->getParent(); // If the destroy is in a consumed block or a final consuming block, // hoisting it would avoid a copy. if (consumedAtExitBlocks.contains(block)) continue; visitor(destroy, PrunedLiveness::LifetimeEnding::Value::Ending); } } void OSSACanonicalizeOwned::extendUnconsumedLiveness( PrunedLivenessBoundary const &boundary) { visitExtendedUnconsumedBoundary( boundary.lastUsers, [&](auto *instruction, auto lifetimeEnding) { liveness->updateForUse(instruction, lifetimeEnding); }); } //===----------------------------------------------------------------------===// // MARK: Step 4. Extend the "original" boundary from step 2 up to destroys that // aren't separated from it by "interesting" instructions. //===----------------------------------------------------------------------===// namespace { /// Extends the boundary from PrunedLiveness down to preexisting destroys of the /// def which aren't separated from the original boundary by "interesting" /// instructions. /// /// The motivation for extending the boundary is to avoid "churning" when /// iterating to a fixed point by canonicalizing the lifetimes of several /// values with overlapping live ranges and failing to find a fixed point /// because their destroys are repeatedly hoisted over one another. class ExtendBoundaryToDestroys final { using InstructionPredicate = llvm::function_ref; SSAPrunedLiveness &liveness; PrunedLivenessBoundary const &originalBoundary; SILValue currentDef; BasicBlockSet seenMergePoints; InstructionPredicate isDestroy; public: ExtendBoundaryToDestroys(SSAPrunedLiveness &liveness, PrunedLivenessBoundary const &originalBoundary, SILValue currentDef, InstructionPredicate isDestroy) : liveness(liveness), originalBoundary(originalBoundary), currentDef(currentDef), seenMergePoints(currentDef->getFunction()), isDestroy(isDestroy){}; ExtendBoundaryToDestroys(ExtendBoundaryToDestroys const &) = delete; ExtendBoundaryToDestroys & operator=(ExtendBoundaryToDestroys const &) = delete; /// Compute the extended boundary by walking out from the original boundary /// (from PrunedLiveness::computeBoundary) down to any destroys that appear /// later but which aren't separated from the original boundary by /// "interesting" users. void extend(PrunedLivenessBoundary &boundary) { for (auto *def : originalBoundary.deadDefs) { extendBoundaryFromDef(def, boundary); } for (auto *destination : originalBoundary.boundaryEdges) { extendBoundaryFromBoundaryEdge(destination, boundary); } for (auto *user : originalBoundary.lastUsers) { extendBoundaryFromUser(user, boundary); } } /// Look past ignoreable instructions to find the _last_ destroy after the /// specified instruction that destroys \p def. static DestroyValueInst *findDestroyAfter(SILInstruction *previous, SILValue def, InstructionPredicate isDestroy) { DestroyValueInst *retval = nullptr; for (auto *instruction = previous->getNextInstruction(); instruction; instruction = instruction->getNextInstruction()) { if (!OSSACanonicalizeOwned::ignoredByDestroyHoisting( instruction->getKind())) break; if (isDestroy(instruction)) retval = cast(instruction); } return retval; } /// Look past ignoreable instructions to find the _last_ destroy at or after /// the specified instruction that destroys \p def. static DestroyValueInst * findDestroyAtOrAfter(SILInstruction *start, SILValue def, InstructionPredicate isDestroy) { if (isDestroy(start)) return cast(start); return findDestroyAfter(start, def, isDestroy); } /// Look past ignoreable instructions to find the _first_ destroy in \p /// destination that destroys \p def and isn't separated from the beginning /// by "interesting" instructions. static DestroyValueInst * findDestroyFromBlockBegin(SILBasicBlock *destination, SILValue def, InstructionPredicate isDestroy) { return findDestroyAtOrAfter(&*destination->begin(), def, isDestroy); } private: /// Compute the points on the extended boundary found by walking forward from /// the dead def (starting either with the top of the block in the case of a /// dead arg or the next instruction in the case of an instruction) down to /// any destroys that appear later but which aren't separated from the /// original boundary by "interesting" users. /// /// If a destroy is found, it becomes a last user. Otherwise, the boundary /// stays in place and \p def remains a dead def. void extendBoundaryFromDef(SILNode *def, PrunedLivenessBoundary &boundary) { if (auto *arg = dyn_cast(def)) { if (auto *dvi = findDestroyFromBlockBegin(arg->getParent(), currentDef, isDestroy)) { boundary.lastUsers.push_back(dvi); return; } } else { if (auto *dvi = findDestroyAfter(cast(def), currentDef, isDestroy)) { boundary.lastUsers.push_back(dvi); return; } } boundary.deadDefs.push_back(def); } /// Compute the points on the extended boundary found by walking down from the /// boundary edge in the original boundary (uniquely determined by the /// specified destination edge) down to any destroys that appear later but /// which aren't separated from the original boundary by "interesting" users. /// /// If a destroy is found, it becomes a last user. Otherwise, the boundary /// stays in place and \p destination remains a boundary edge. void extendBoundaryFromBoundaryEdge(SILBasicBlock *destination, PrunedLivenessBoundary &boundary) { if (auto *dvi = findDestroyFromBlockBegin(destination, currentDef, isDestroy)) { boundary.lastUsers.push_back(dvi); } else { boundary.boundaryEdges.push_back(destination); } } /// Compute the points on the extended boundary found by walking down from the /// specified instruction in the original boundary down to any destroys that /// appear later but which aren't separated from the original boundary by /// "interesting" users. /// /// If the user is consuming, the boundary remains in place. /// /// If the user is a terminator, see extendBoundaryFromTerminator. /// /// If a destroy is found after the (non-consuming, non-terminator) \p user, /// it becomes a last user. Otherwise, the boundary stays in place and \p /// user remains a last user. void extendBoundaryFromUser(SILInstruction *user, PrunedLivenessBoundary &boundary) { if (isDestroy(user)) { auto *dvi = cast(user); auto *existingDestroy = findDestroyAtOrAfter(dvi, currentDef, isDestroy); assert(existingDestroy && "couldn't find a destroy at or after one!?"); boundary.lastUsers.push_back(existingDestroy); return; } switch (liveness.isInterestingUser(user)) { case PrunedLiveness::IsInterestingUser::LifetimeEndingUse: // Even if we saw a destroy after this consuming use, we don't want to // add it to the boundary. We will rewrite copies so that this user is // the final consuming user on this path. boundary.lastUsers.push_back(user); return; case PrunedLiveness::IsInterestingUser::NonLifetimeEndingUse: case PrunedLiveness::IsInterestingUser::NonUser: if (auto *terminator = dyn_cast(user)) { extendBoundaryFromTerminator(terminator, boundary); return; } if (auto *existingDestroy = findDestroyAfter(user, currentDef, isDestroy)) { boundary.lastUsers.push_back(existingDestroy); return; } boundary.lastUsers.push_back(user); } } /// Compute the points on the extended boundary by walking into \p user's /// parent's successors and looking for destroys. /// /// If any destroys are found, they become last users and all other successors /// (which lack destroys) become boundary edges. If no destroys are found, /// the boundary stays in place and \p user remains a last user. void extendBoundaryFromTerminator(TermInst *user, PrunedLivenessBoundary &boundary) { auto *block = user->getParent(); // Record the successors at the beginning of which we didn't find destroys. // If we found a destroy at the beginning of any other successor, then all // the other edges become boundary edges. SmallVector successorsWithoutDestroys; bool foundDestroy = false; for (auto *successor : block->getSuccessorBlocks()) { // If multiple terminators were live and had the same successor, only // record the boundary corresponding to that destination block once. if (!seenMergePoints.insert(successor)) { // Thanks to the lack of critical edges, having seen this successor // before means it has multiple predecessors, so this must be \p block's // unique successor. assert(block->getSingleSuccessorBlock() == successor); // When this merge point was encountered the first time, a // destroy_value was sought from its top. If one was found, it was // added to the boundary. If no destroy_value was found, _that_ user // (i.e. the one on behalf of which extendBoundaryFromTerminator was // called which inserted successor into seenMergePoints) was added to // the boundary. // // This time, if a destroy was found, it's already in the boundary. If // no destroy was found, though, _this_ user must be added to the // boundary. foundDestroy = findDestroyFromBlockBegin(successor, currentDef, isDestroy); continue; } if (auto *dvi = findDestroyFromBlockBegin(successor, currentDef, isDestroy)) { boundary.lastUsers.push_back(dvi); foundDestroy = true; } else { successorsWithoutDestroys.push_back(successor); } } if (foundDestroy) { // If we found a destroy in any successor, then every block at the // beginning of which we didn't find a destroy becomes a boundary edge. for (auto *successor : successorsWithoutDestroys) { boundary.boundaryEdges.push_back(successor); } } else { boundary.lastUsers.push_back(user); } } }; } // anonymous namespace void OSSACanonicalizeOwned::findExtendedBoundary( PrunedLivenessBoundary const &originalBoundary, PrunedLivenessBoundary &boundary) { assert(boundary.lastUsers.size() == 0 && boundary.boundaryEdges.size() == 0 && boundary.deadDefs.size() == 0); auto isDestroy = [&](auto *inst) { return destroys.contains(inst); }; ExtendBoundaryToDestroys extender(*liveness, originalBoundary, getCurrentDef(), isDestroy); extender.extend(boundary); } //===----------------------------------------------------------------------===// // MARK: Step 5. Insert destroys onto the boundary found in step 3 where needed. //===----------------------------------------------------------------------===// /// Create a new destroy_value instruction before the specified instruction and /// record it as a final consume. static void insertDestroyBeforeInstruction(SILInstruction *nextInstruction, SILValue currentDef, IsDeadEnd_t isDeadEnd, CanonicalOSSAConsumeInfo &consumes, SmallVectorImpl &destroys, InstModCallbacks &callbacks) { // OSSACompleteLifetime: This conditional clause can be deleted with // complete lifetimes. if (consumes.isUnreachableLifetimeEnd(nextInstruction)) { // Don't create a destroy_value if the next instruction is an unreachable // (or a terminator on the availability boundary of the dead-end region // starting from the non-lifetime-ending boundary of `currentDef`). // // If there was a destroy here already, it would be reused. Avoids // creating an explicit destroy of a value which might have an unclosed // borrow scope. Doing so would result in // // somewhere: // %def // %borrow = begin_borrow ... // // die: // destroy_value %def // unreachable // // which is invalid (although the verifier doesn't catch // it--rdar://115850528) because there must be an `end_borrow %borrow` // before the destroy_value. return; } SILBuilderWithScope builder(nextInstruction); auto loc = RegularLocation::getAutoGeneratedLocation(nextInstruction->getLoc()); auto *dvi = builder.createDestroyValue(loc, currentDef, DontPoisonRefs, isDeadEnd); callbacks.createdNewInst(dvi); consumes.recordFinalConsume(dvi); ++NumDestroysGenerated; destroys.push_back(dvi); } /// Whether a destroy created at \p inst should be marked [dead_end]. /// /// It should be if /// (1) \p inst is itself in a dead-end region /// (2) all destroys after \p inst are [dead_end] static IsDeadEnd_t isDeadEndDestroy(SILInstruction *inst, SmallPtrSetVector const &destroys, BasicBlockSet &semanticDestroysBlocks, DeadEndBlocks *deadEnds) { auto *parent = inst->getParent(); if (!deadEnds->isDeadEnd(parent)) { // Only destroys in dead-ends can be non-meaningful (aka "dead end"). return IsntDeadEnd; } if (semanticDestroysBlocks.contains(parent)) { // `parent` has a semantic destroy somewhere. Is it after `inst`? for (auto *i = inst; i; i = i->getNextInstruction()) { if (!destroys.contains(i)) { continue; } auto *dvi = cast(i); if (!dvi->isDeadEnd()) { // Some subsequent destroy within `parent` was meaningful, so one // created at `inst` must be too. return IsntDeadEnd; } } } // Walk the portion of the dead-end region after `parent` to check that all // destroys are non-meaningful. BasicBlockWorklist worklist(inst->getFunction()); for (auto *successor : parent->getSuccessorBlocks()) { worklist.push(successor); } while (auto *block = worklist.pop()) { assert(deadEnds->isDeadEnd(block)); if (semanticDestroysBlocks.contains(block)) { // Some subsequent destroy was meaningful, so one created at `inst` // must be too. return IsntDeadEnd; } for (auto *successor : block->getSuccessorBlocks()) { worklist.pushIfNotVisited(successor); } } return IsDeadEnd; } /// Inserts destroys along the boundary where needed and records all final /// consuming uses. /// /// Observations: /// - currentDef must be postdominated by some subset of its /// consuming uses, including destroys on all return paths. /// - The postdominating consumes cannot be within nested loops. /// - Any blocks in nested loops are now marked LiveOut. void OSSACanonicalizeOwned::insertDestroysOnBoundary( PrunedLivenessBoundary const &boundary, SmallVectorImpl &newDestroys) { BasicBlockSet semanticDestroyBlocks(getCurrentDef()->getFunction()); for (auto *destroy : destroys) { if (!cast(destroy)->isDeadEnd()) { semanticDestroyBlocks.insert(destroy->getParent()); } } auto isDeadEnd = [&semanticDestroyBlocks, this](SILInstruction *inst) -> IsDeadEnd_t { return isDeadEndDestroy( inst, destroys, semanticDestroyBlocks, deadEndBlocksAnalysis->get(getCurrentDef()->getFunction())); }; BasicBlockSet seenMergePoints(getCurrentDef()->getFunction()); for (auto *instruction : boundary.lastUsers) { if (destroys.contains(instruction)) { consumes.recordFinalConsume(instruction); continue; } switch (liveness->isInterestingUser(instruction)) { case PrunedLiveness::IsInterestingUser::LifetimeEndingUse: consumes.recordFinalConsume(instruction); continue; case PrunedLiveness::IsInterestingUser::NonLifetimeEndingUse: case PrunedLiveness::IsInterestingUser::NonUser: if (isa(instruction)) { auto *block = instruction->getParent(); for (auto *successor : block->getSuccessorBlocks()) { if (!seenMergePoints.insert(successor)) { assert(block->getSingleSuccessorBlock() == successor); continue; } auto *insertionPoint = &*successor->begin(); insertDestroyBeforeInstruction(insertionPoint, getCurrentDef(), isDeadEnd(insertionPoint), consumes, newDestroys, getCallbacks()); LLVM_DEBUG(llvm::dbgs() << " Destroy after terminator " << *instruction << " at beginning of "; successor->printID(llvm::dbgs(), false); llvm::dbgs() << "\n";); } continue; } auto *insertionPoint = instruction->getNextInstruction(); insertDestroyBeforeInstruction(insertionPoint, getCurrentDef(), isDeadEnd(insertionPoint), consumes, newDestroys, getCallbacks()); LLVM_DEBUG(llvm::dbgs() << " Destroy at last use " << insertionPoint << "\n"); continue; } } for (auto *edgeDestination : boundary.boundaryEdges) { auto *insertionPoint = &*edgeDestination->begin(); insertDestroyBeforeInstruction(insertionPoint, getCurrentDef(), isDeadEnd(insertionPoint), consumes, newDestroys, getCallbacks()); LLVM_DEBUG(llvm::dbgs() << " Destroy on edge " << edgeDestination << "\n"); } for (auto *def : boundary.deadDefs) { if (auto *arg = dyn_cast(def)) { auto *insertionPoint = &*arg->getParent()->begin(); insertDestroyBeforeInstruction(insertionPoint, getCurrentDef(), isDeadEnd(insertionPoint), consumes, newDestroys, getCallbacks()); LLVM_DEBUG(llvm::dbgs() << " Destroy after dead def arg " << arg << "\n"); } else { auto *instruction = cast(def); auto *insertionPoint = instruction->getNextInstruction(); assert(insertionPoint && "def instruction was a terminator?!"); insertDestroyBeforeInstruction(insertionPoint, getCurrentDef(), isDeadEnd(insertionPoint), consumes, newDestroys, getCallbacks()); LLVM_DEBUG(llvm::dbgs() << " Destroy after dead def inst " << instruction << "\n"); } } } //===----------------------------------------------------------------------===// // MARK: Step 6. Rewrite copies and destroys //===----------------------------------------------------------------------===// /// The lifetime extends beyond given consuming use. Copy the value. /// /// This can set the operand value, but cannot invalidate the use iterator. void swift::copyLiveUse(Operand *use, InstModCallbacks &instModCallbacks) { SILInstruction *user = use->getUser(); SILBuilderWithScope builder(user->getIterator()); auto loc = RegularLocation::getAutoGeneratedLocation(user->getLoc()); auto *copy = builder.createCopyValue(loc, use->get()); instModCallbacks.createdNewInst(copy); use->set(copy); ++NumCopiesGenerated; LLVM_DEBUG(llvm::dbgs() << " Copying at last use " << *copy); } /// Revisit the def-use chain of currentDef. Mark unneeded original /// copies and destroys for deletion. Insert new copies for interior uses that /// require ownership of the used operand. void OSSACanonicalizeOwned::rewriteCopies( SmallVectorImpl const &newDestroys) { assert(getCurrentDef()->getOwnershipKind() == OwnershipKind::Owned); // Shadow discoveredDefs in order to constrain its uses. const auto &discoveredDefs = this->discoveredDefs; InstructionSetVector instsToDelete(getCurrentDef()->getFunction()); // Visit each operand in the def-use chain. // // Return true if the operand can use the current definition. Return false if // it requires a copy. auto visitUse = [&](Operand *use) { auto *user = use->getUser(); if (destroys.contains(user)) { auto *destroy = cast(user); // If this destroy was marked as a final destroy, ignore it; otherwise, // delete it. if (!consumes.claimConsume(destroy)) { instsToDelete.insert(destroy); LLVM_DEBUG(llvm::dbgs() << " Removing " << *destroy); ++NumDestroysEliminated; } else if (pruneDebugMode) { // If this destroy was marked as a final destroy, add it to liveness so // that we don't delete any debug instructions that occur before it. // (Only relevant in pruneDebugMode). liveness->updateForUse(destroy, /*lifetimeEnding*/ true); } return true; } // Nonconsuming uses do not need copies and cannot be marked as destroys. // A lifetime-ending use here must be a consume because EndBorrow/Reborrow // uses have been filtered out. if (!use->isLifetimeEnding()) return true; // If this use was not marked as a final destroy *or* this is not the first // consumed operand we visited, then it needs a copy. if (!consumes.claimConsume(user)) { return false; } return true; }; // Perform a def-use traversal, visiting each use operand. for (auto def : discoveredDefs) { switch (def.kind) { case Def::BorrowedFrom: case Def::Reborrow: // Direct uses of these defs never need to be rewritten. Being guaranteed // values, none of their direct uses consume an owned value. assert(def.value->getOwnershipKind() == OwnershipKind::Guaranteed); break; case Def::Root: { SILValue value = def.value; for (auto useIter = value->use_begin(), endIter = value->use_end(); useIter != endIter;) { Operand *use = *useIter++; if (!visitUse(use)) { copyLiveUse(use, getCallbacks()); } } break; } case Def::Copy: { SILValue value = def.value; CopyValueInst *srcCopy = cast(value); // Recurse through copies while replacing their uses. Operand *reusedCopyOp = nullptr; for (auto useIter = srcCopy->use_begin(); useIter != srcCopy->use_end();) { Operand *use = *useIter++; if (!visitUse(use)) { if (!reusedCopyOp && srcCopy->getParent() == use->getParentBlock()) { reusedCopyOp = use; } else { copyLiveUse(use, getCallbacks()); } } } if (!(reusedCopyOp && srcCopy->hasOneUse())) { getCallbacks().replaceValueUsesWith(srcCopy, srcCopy->getOperand()); if (reusedCopyOp) { reusedCopyOp->set(srcCopy); } else { if (instsToDelete.insert(srcCopy)) { LLVM_DEBUG(llvm::dbgs() << " Removing " << *srcCopy); ++NumCopiesAndMovesEliminated; } } } break; } } } assert(!consumes.hasUnclaimedConsumes()); if (pruneDebugMode) { for (auto *destroy : newDestroys) { liveness->updateForUse(destroy, /*lifetimeEnding=*/true); } for (auto *dvi : debugValues) { if (liveness->isWithinBoundary( dvi, deadEndBlocksAnalysis->get(getCurrentDef()->getFunction()))) { continue; } LLVM_DEBUG(llvm::dbgs() << " Removing debug_value: " << *dvi); deleter.forceDelete(dvi); } } // Remove the leftover copy_value and destroy_value instructions. for (auto *inst : instsToDelete) { deleter.forceDelete(inst); } } //===----------------------------------------------------------------------===// // MARK: Top-Level API //===----------------------------------------------------------------------===// bool OSSACanonicalizeOwned::computeLiveness() { LLVM_DEBUG(llvm::dbgs() << " Canonicalizing: " << currentDef); if (currentDef->getOwnershipKind() != OwnershipKind::Owned) { LLVM_DEBUG(llvm::dbgs() << " not owned, never mind\n"); return false; } // Note: There is no need to register callbacks with this utility. 'onDelete' // is the only one in use to handle dangling pointers, which could be done // instead be registering a temporary handler with the pass. Canonicalization // is only allowed to create and delete instructions that are associated with // this canonical def (copies and destroys). Each canonical def has a disjoint // extended lifetime. Any pass calling this utility should work at the level // canonical defs, not individual instructions. // // NotifyWillBeDeleted will not work because copy rewriting removes operands // before deleting instructions. Also prohibit setUse callbacks just because // that would simply be unsound. assert(!getCallbacks().notifyWillBeDeletedFunc && !getCallbacks().setUseValueFunc && "unsupported"); // Step 1: compute liveness if (!computeCanonicalLiveness()) { LLVM_DEBUG(llvm::dbgs() << "Failed to compute canonical liveness?!\n"); clear(); return false; } if (respectsDeadEnds() && hasAnyDeadEnds()) { if (respectsDeinitBarriers()) { extendLexicalLivenessToDeadEnds(); } extendLivenessToDeadEnds(); } if (respectsDeinitBarriers()) { extendLivenessToDeinitBarriers(); } if (accessBlockAnalysis) { extendLivenessThroughOverlappingAccess(); } return true; } void OSSACanonicalizeOwned::rewriteLifetimes() { // Step 2: compute original boundary PrunedLivenessBoundary originalBoundary; findOriginalBoundary(originalBoundary); PrunedLivenessBoundary extendedBoundary; if (maximizeLifetime) { // Step 3. (optional) maximize lifetimes extendUnconsumedLiveness(originalBoundary); originalBoundary.clear(); // Step 2: (again) recompute the original boundary since we've extended // liveness findOriginalBoundary(originalBoundary); // Step 4: extend boundary to destroys findExtendedBoundary(originalBoundary, extendedBoundary); } else { // Step 3: (skipped) // Step 4: extend boundary to destroys findExtendedBoundary(originalBoundary, extendedBoundary); } SmallVector newDestroys; // Step 5: insert destroys and record consumes insertDestroysOnBoundary(extendedBoundary, newDestroys); // Step 6: rewrite copies and delete extra destroys rewriteCopies(newDestroys); clear(); } /// Canonicalize a single extended owned lifetime. bool OSSACanonicalizeOwned::canonicalizeValueLifetime( SILValue def, ArrayRef lexicalLifetimeEnds) { LivenessState livenessState(*this, def, lexicalLifetimeEnds); // Don't canonicalize the lifetimes of values of move-only type. According to // language rules, they are fixed. if (def->getType().isMoveOnly()) { return false; } // Step 1: Compute liveness. if (!computeLiveness()) { LLVM_DEBUG(llvm::dbgs() << "Failed to compute liveness boundary!\n"); return false; } // Steps 2-6. \see rewriteUses for explanation of steps 2-6. rewriteLifetimes(); return true; } namespace swift::test { // Arguments: // - bool: pruneDebug // - bool: maximizeLifetimes // - bool: "respectAccessScopes", whether to contract lifetimes to end within // access scopes which they previously enclosed but can't be hoisted // before // - SILValue: value to canonicalize // - [SILInstruction]: the lexicalLifetimeEnds to recognize // Dumps: // - function after value canonicalization static FunctionTest OSSACanonicalizeOwnedTest( "ossa_canonicalize_lifetime", [](auto &function, auto &arguments, auto &test) { auto *accessBlockAnalysis = test.template getAnalysis(); auto *deadEndBlocksAnalysis = test.template getAnalysis(); auto *dominanceAnalysis = test.template getAnalysis(); DominanceInfo *domTree = dominanceAnalysis->get(&function); auto *calleeAnalysis = test.template getAnalysis(); auto pruneDebug = PruneDebugInsts_t(arguments.takeBool()); auto maximizeLifetimes = MaximizeLifetime_t(arguments.takeBool()); auto respectAccessScopes = arguments.takeBool(); InstructionDeleter deleter; OSSACanonicalizeOwned canonicalizer( pruneDebug, maximizeLifetimes, &function, respectAccessScopes ? accessBlockAnalysis : nullptr, deadEndBlocksAnalysis, domTree, calleeAnalysis, deleter); auto value = arguments.takeValue(); SmallVector lexicalLifetimeEnds; while (arguments.hasUntaken()) { lexicalLifetimeEnds.push_back(arguments.takeInstruction()); } canonicalizer.canonicalizeValueLifetime(value, lexicalLifetimeEnds); function.print(llvm::outs()); }); } // end namespace swift::test //===----------------------------------------------------------------------===// // MARK: Debugging //===----------------------------------------------------------------------===// SWIFT_ASSERT_ONLY_DECL(void CanonicalOSSAConsumeInfo::dump() const { llvm::dbgs() << "Consumes:"; for (auto &blockAndInst : finalBlockConsumes) { llvm::dbgs() << " " << *blockAndInst.getSecond(); } })