//===--------- CopyPropagation.cpp - Remove redundant SSA copies. ---------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2018 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 // //===----------------------------------------------------------------------===// /// /// SSA Copy propagation pass to remove unnecessary copy_value and destroy_value /// instructions. /// /// Because this algorithm rewrites copies and destroys without attempting /// to balance the retain count, it is only sound for ownership-SSA. /// /// [WIP] This complements -enable-sil-opaque-values. It is not yet enabled by /// default. It can be enabled once the ownership properties, currently /// specified as helpers at the top of this file, are formalized via a central /// API. /// /// The pass has four steps: /// /// 1. Find SSA defs that have been copied. /// /// Then, separately, for each copied def, perform steps 2-4: /// /// 2. Compute "pruned" liveness of the def and its copies, ignoring original /// destroys. /// /// 3. Find the SSA def's final destroy points based on its pruned liveness. /// /// 4. Rewrite the def's original copies and destroys, inserting new copies /// where needed. /// /// The state used by this pass is encapsulated in CopyPropagationState, simply /// refered to as `pass`: /// /// - Step 1 initializes `pass.currDef`. /// /// - Step 2 initializes `pass.liveness`. /// /// - Step 3 initializes `pass.destroys` and inserts destroy_value instructions. /// /// - Step 4 deletes original copies and destroys and inserts new copies. /// /// Example #1: Handle consuming and nonconsuming uses. /// /// bb0(%arg : @owned $T, %addr : @trivial $*T): /// %copy = copy_value %arg : $T /// debug_value %copy : $T /// store %copy to [init] %addr : $*T /// debug_value %arg : $T /// debug_value_addr %addr : $*T /// destroy_value %arg : $T /// /// Will be transformed to: /// /// bb0(%arg : @owned $T, %addr : @trivial $*T): /// (The original copy is deleted.) /// debug_value %arg : $T /// (A new copy_value is inserted before the consuming store.) /// %copy = copy_value %arg : $T /// store %copy to [init] %addr : $*T /// (The non-consuming use now uses the original value.) /// debug_value %arg : $T /// (A new destroy is inserted after the last use.) /// destroy_value %arg : $T /// debug_value_addr %addr : $*T /// (The original destroy is deleted.) /// /// Example #2: Handle control flow. /// /// bb0(%arg : @owned $T): /// cond_br %_, bb1, bxb2 /// /// bb1: /// debug_value %arg : $T /// %copy = copy_value %arg : $T /// destroy_value %copy : $T /// br bb2 /// /// bb2: /// destroy_value %arg : $T /// /// Will be transformed to: /// /// bb0(%arg : @owned $T, %addr : @trivial $*T, %z : @trivial $Builtin.Int1): /// cond_br %z, bb1, bb3 /// /// bb1: /// (The critical edge is split.) /// destroy_value %arg : $T /// br bb2 /// /// bb3: /// (The original copy is deleted.) /// debug_value %arg : $T /// destroy_value %arg : $T /// br bb2 /// /// bb2: /// (The original destroy is deleted.) /// /// FIXME: When we ban critical edges in SIL, this pass does not need to /// invalidate any CFG analyses. /// /// FIXME: SILGen currently emits spurious borrow scopes which block /// elimination of copies within a scope. /// /// TODO: Possibly extend this algorithm to better handle aggregates. If /// ownership SIL consistently uses a `destructure` operation to split /// aggregates, then the current scalar algorithm should work /// naturally. However, if we instead need to handle borrow scopes, then this /// becomes much harder. We would need a DI-style tracker to track the uses of a /// particular copied def. The single boolean "live" state would be replaced /// with a bitvector that tracks each tuple element. /// /// TODO: Cleanup the resulting SIL. Delete instructions with no side effects /// that produce values which are immediately destroyed after copy propagation. /// ===----------------------------------------------------------------------=== #define DEBUG_TYPE "copy-propagation" #include "swift/SIL/InstructionUtils.h" #include "swift/SIL/Projection.h" #include "swift/SILOptimizer/PassManager/Passes.h" #include "swift/SILOptimizer/PassManager/Transforms.h" #include "swift/SILOptimizer/Utils/CFG.h" #include "swift/SILOptimizer/Utils/IndexTrie.h" #include "swift/SILOptimizer/Utils/Local.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/Statistic.h" using namespace swift; using llvm::DenseMap; using llvm::DenseSet; using llvm::SmallSetVector; STATISTIC(NumCopiesEliminated, "number of copy_value instructions removed"); STATISTIC(NumDestroysEliminated, "number of destroy_value instructions removed"); STATISTIC(NumCopiesGenerated, "number of copy_value instructions created"); STATISTIC(NumDestroysGenerated, "number of destroy_value instructions created"); STATISTIC(NumUnknownUsers, "number of functions with unknown users"); //===----------------------------------------------------------------------===// // Ownership Abstraction. // // FIXME: These helpers are only defined in this pass for prototyping. After // bootstrapping, they should be moved to a central ownership API (shared by // SILOwnershipVerifier, etc.). // // Categories of owned value users. (U1-U2 apply to any value, O3-O5 only apply // to owned values). // // U1. Use the value instantaneously (copy_value, @guaranteed). // // U2. Escape the nontrivial contents of the value (ref_to_unowned, // unchecked_trivial_bitcast). // // O3. Propagate the value without consuming it (mark_dependence, begin_borrow). // // O4. Consume the value immediately (store, destroy, @owned, destructure). // // O5. Consume the value indirectly via a move (tuple, struct). // ===---------------------------------------------------------------------===// // TODO: Figure out how to handle these cases if possible. static bool isUnknownUse(Operand *use) { switch (use->getUser()->getKind()) { default: return false; // FIXME: (Category O3) mark_dependence requires recursion to find all // uses. It should be replaced by begin/end dependence. case SILInstructionKind::MarkDependenceInst: // Dependent // FIXME: (Category O3) ref_tail_addr should require a borrow because it // doesn't rely on fix_lifetime like other escaping instructions. case SILInstructionKind::RefTailAddrInst: // FIXME: (Category O3) dynamic_method_br seems to capture self, presumably // propagating lifetime. This should probably borrow self, then be treated // like mark_dependence. case SILInstructionKind::DynamicMethodBranchInst: // FIXME: (Category O3) The ownership verifier says project_box can accept an // owned value as a normal use, but it projects the address. That's either an // ownership bug or a special case. case SILInstructionKind::ProjectBoxInst: case SILInstructionKind::ProjectExistentialBoxInst: // FIXME: (Category O3) The ownership verifier says open_existential_box can // accept an owned value as a normal use, but it projects an address. case SILInstructionKind::OpenExistentialBoxInst: // Unmanaged operations hopefully don't apply to the same value as CopyValue? case SILInstructionKind::UnmanagedRetainValueInst: case SILInstructionKind::UnmanagedReleaseValueInst: case SILInstructionKind::UnmanagedAutoreleaseValueInst: return true; } } /// Return true if the given owned operand is consumed by the given call. static bool isAppliedArgConsumed(ApplySite apply, Operand *oper) { ParameterConvention paramConv; if (oper->get() == apply.getCallee()) { assert(oper->getOperandNumber() == 0 && "function can't be passed to itself"); paramConv = apply.getSubstCalleeType()->getCalleeConvention(); } else { unsigned argIndex = apply.getCalleeArgIndex(*oper); paramConv = apply.getSubstCalleeConv() .getParamInfoForSILArg(argIndex) .getConvention(); } return isConsumedParameter(paramConv); } /// Return true if the given builtin consumes its operand. static bool isBuiltinArgConsumed(BuiltinInst *BI) { const BuiltinInfo &Builtin = BI->getBuiltinInfo(); switch (Builtin.ID) { default: llvm_unreachable("Unexpected Builtin with owned value operand."); // Extend lifetime without consuming. case BuiltinValueKind::ErrorInMain: case BuiltinValueKind::UnexpectedError: case BuiltinValueKind::WillThrow: return false; // UnsafeGuaranteed moves the value, which will later be destroyed. case BuiltinValueKind::UnsafeGuaranteed: return true; } } /// Return true if the given operand is consumed by its user. /// /// TODO: Review the semantics of operations that extend the lifetime *without* /// propagating the value. Ideally, that never happens without borrowing first. static bool isConsuming(Operand *use) { auto *user = use->getUser(); if (isa(user)) return isAppliedArgConsumed(ApplySite(user), use); if (auto *BI = dyn_cast(user)) return isBuiltinArgConsumed(BI); switch (user->getKind()) { default: llvm::dbgs() << *user; llvm_unreachable("Unexpected use of a loadable owned value."); // Consume the value. case SILInstructionKind::AutoreleaseValueInst: case SILInstructionKind::DeallocBoxInst: case SILInstructionKind::DeallocExistentialBoxInst: case SILInstructionKind::DeallocRefInst: case SILInstructionKind::DeinitExistentialValueInst: case SILInstructionKind::DestroyValueInst: case SILInstructionKind::EndLifetimeInst: case SILInstructionKind::InitExistentialRefInst: case SILInstructionKind::InitExistentialValueInst: case SILInstructionKind::KeyPathInst: case SILInstructionKind::ReleaseValueInst: case SILInstructionKind::ReleaseValueAddrInst: case SILInstructionKind::StoreInst: case SILInstructionKind::StrongReleaseInst: case SILInstructionKind::UnownedReleaseInst: case SILInstructionKind::UnconditionalCheckedCastValueInst: return true; // Terminators must consume their owned values. case SILInstructionKind::BranchInst: case SILInstructionKind::CheckedCastBranchInst: case SILInstructionKind::CheckedCastValueBranchInst: case SILInstructionKind::CondBranchInst: case SILInstructionKind::ReturnInst: case SILInstructionKind::ThrowInst: return true; case SILInstructionKind::DeallocPartialRefInst: return cast(user)->getInstance() == use->get(); // Move the value. case SILInstructionKind::TupleInst: case SILInstructionKind::StructInst: case SILInstructionKind::ObjectInst: case SILInstructionKind::EnumInst: case SILInstructionKind::OpenExistentialRefInst: case SILInstructionKind::UpcastInst: case SILInstructionKind::UncheckedRefCastInst: case SILInstructionKind::ConvertFunctionInst: case SILInstructionKind::RefToBridgeObjectInst: case SILInstructionKind::BridgeObjectToRefInst: case SILInstructionKind::UnconditionalCheckedCastInst: case SILInstructionKind::MarkUninitializedInst: case SILInstructionKind::UncheckedEnumDataInst: case SILInstructionKind::DestructureStructInst: case SILInstructionKind::DestructureTupleInst: return true; // BeginBorrow should already be skipped. // EndBorrow extends the lifetime like a normal use. case SILInstructionKind::EndBorrowInst: return false; // Extend the lifetime without borrowing, propagating, or destroying it. case SILInstructionKind::BridgeObjectToWordInst: case SILInstructionKind::ClassMethodInst: case SILInstructionKind::CopyBlockInst: case SILInstructionKind::CopyValueInst: case SILInstructionKind::DebugValueInst: case SILInstructionKind::ExistentialMetatypeInst: case SILInstructionKind::FixLifetimeInst: case SILInstructionKind::SelectEnumInst: case SILInstructionKind::SetDeallocatingInst: case SILInstructionKind::StoreWeakInst: case SILInstructionKind::ValueMetatypeInst: return false; // Escape the value. The lifetime must already be enforced via something like // fix_lifetime. case SILInstructionKind::RefToRawPointerInst: case SILInstructionKind::RefToUnmanagedInst: case SILInstructionKind::RefToUnownedInst: case SILInstructionKind::UncheckedBitwiseCastInst: case SILInstructionKind::UncheckedTrivialBitCastInst: return false; // Dynamic dispatch without capturing self. case SILInstructionKind::ObjCMethodInst: case SILInstructionKind::ObjCSuperMethodInst: case SILInstructionKind::SuperMethodInst: case SILInstructionKind::WitnessMethodInst: return false; } } //===----------------------------------------------------------------------===// // CopyPropagationState: shared state for the pass's analysis and transforms. //===----------------------------------------------------------------------===// namespace { /// LiveWithin blocks have at least one use and/or def within the block, but are /// not LiveOut. /// /// LiveOut blocks are live on at least one successor path. /// /// Dead blocks are either outside of the def's pruned liveness region, or they /// have not yet been discovered by the liveness computation. enum IsLive_t { LiveWithin, LiveOut, Dead }; /// Liveness information, which is produced by step #2, computeLiveness, and /// consumed by step #3, findOrInsertDestroys. class LivenessInfo { // Map of all blocks in which current def is live. True if it is also liveout. DenseMap liveBlocks; // Set of all "interesting" users in this def's live range and whether their // used value is consumed. (Non-consuming uses within a block that is already // known to be live are uninteresting.) DenseMap users; // Original points in the CFG where the current value was consumed or // destroyed. typedef SmallSetVector BlockSetVec; BlockSetVec originalDestroyBlocks; public: bool empty() { assert(!liveBlocks.empty() || users.empty()); return liveBlocks.empty(); } void clear() { liveBlocks.clear(); users.clear(); originalDestroyBlocks.clear(); } IsLive_t isBlockLive(SILBasicBlock *bb) const { auto liveBlockIter = liveBlocks.find(bb); if (liveBlockIter == liveBlocks.end()) return Dead; return liveBlockIter->second ? LiveOut : LiveWithin; } void markBlockLive(SILBasicBlock *bb, IsLive_t isLive) { assert(isLive != Dead && "erasing live blocks isn't implemented."); liveBlocks[bb] = (isLive == LiveOut); } // Return a valid result if the given user was identified as an interesting // use of the current def. Returns true if the interesting use consumes the // current def. Optional isConsumingUser(SILInstruction *user) const { auto useIter = users.find(user); if (useIter == users.end()) return None; return useIter->second; } // Record the user of this operand in the set of interesting users. // // Note that a user may use the current value from multiple operands. If any // of the uses are non-consuming, then we must consider the use itself // non-consuming; it cannot be a final destroy point because the value of the // non-consuming operand must be kept alive until the end of the // user. Consider a call that takes the same value using different // conventions: // // apply %f(%val, %val) : $(@guaranteed, @owned) -> () // // This call cannot be allowed to destroy %val. void recordUser(Operand *use) { bool consume = isConsuming(use); auto iterAndSuccess = users.try_emplace(use->getUser(), consume); if (!iterAndSuccess.second) iterAndSuccess.first->second &= consume; } void recordOriginalDestroy(Operand *use) { originalDestroyBlocks.insert(use->getUser()->getParent()); } llvm::iterator_range getOriginalDestroyBlocks() const { return originalDestroyBlocks; } }; /// Information about final destroy points, which is produced by step #3, /// findOrInsertDestroys, and consumed by step #4, rewriteCopies. /// /// This remains valid during copy rewriting. The only instructions referenced /// are destroys that cannot be deleted. /// /// For non-owned values, this remains empty. class DestroyInfo { // Map blocks that contain a final destroy to the destroying instruction. DenseMap finalBlockDestroys; public: bool empty() const { return finalBlockDestroys.empty(); } void clear() { finalBlockDestroys.clear(); } bool hasFinalDestroy(SILBasicBlock *bb) const { return finalBlockDestroys.count(bb); } // Return true if this instruction is marked as a final destroy point of the // current def's live range. A destroy can only be claimed once because // instructions like `tuple` can consume the same value via multiple operands. bool claimDestroy(SILInstruction *inst) { auto destroyPos = finalBlockDestroys.find(inst->getParent()); if (destroyPos != finalBlockDestroys.end() && destroyPos->second == inst) { finalBlockDestroys.erase(destroyPos); return true; } return false; } void recordFinalDestroy(SILInstruction *inst) { finalBlockDestroys[inst->getParent()] = inst; } void invalidateFinalDestroy(SILInstruction *inst) { finalBlockDestroys[inst->getParent()] = inst; } }; /// This pass' shared state. struct CopyPropagationState { SILFunction *F; // Per-function invalidation state. unsigned invalidation; // Current copied def for which this state describes the liveness. SILValue currDef; // computeLiveness result. LivenessInfo liveness; // findOrInsertDestroys result. DestroyInfo destroys; CopyPropagationState(SILFunction *F) : F(F), invalidation(SILAnalysis::InvalidationKind::Nothing) {} bool isValueOwned() const { return currDef.getOwnershipKind() == ValueOwnershipKind::Owned; } void markInvalid(SILAnalysis::InvalidationKind kind) { invalidation |= (unsigned)kind; } void resetDef(SILValue def) { // Do not clear invalidation. It accumulates for an entire function before // the PassManager is notified. liveness.clear(); destroys.clear(); currDef = def; } }; } // namespace //===----------------------------------------------------------------------===// // Step 2. Discover "pruned" liveness for a copied def, ignoring copies and // destroys. // // Generate pass.liveness. // - marks blocks as LiveOut or LiveWithin. // - records users that may become the last user on that path. // - records blocks in which currDef may be destroyed. // // TODO: Make sure all dependencies are accounted for (mark_dependence, // ref_element_addr, project_box?). We should have an ownership API so that the // pass doesn't require any special knowledge of value dependencies. //===----------------------------------------------------------------------===// /// Mark blocks live during a reverse CFG traversal from one specific block /// containing a user. static void computeUseBlockLiveness(SILBasicBlock *userBB, CopyPropagationState &pass) { // If we are visiting this block, then it is not already LiveOut. Mark it // LiveWithin to indicate a liveness boundary within the block. pass.liveness.markBlockLive(userBB, LiveWithin); SmallVector predBBWorklist({userBB}); while (!predBBWorklist.empty()) { SILBasicBlock *bb = predBBWorklist.pop_back_val(); // The popped `bb` is live; mark all its predecessors LiveOut. // pass.currDef was already marked LiveWithin, which terminates traversal. for (auto *predBB : bb->getPredecessorBlocks()) { switch (pass.liveness.isBlockLive(predBB)) { case Dead: predBBWorklist.push_back(predBB); LLVM_FALLTHROUGH; case LiveWithin: pass.liveness.markBlockLive(predBB, LiveOut); break; case LiveOut: break; } } } } /// Update the current def's liveness based on one specific use operand. /// /// Terminators consume their owned operands, so they are not live out of the /// block. static void computeUseLiveness(Operand *use, CopyPropagationState &pass) { auto *bb = use->getUser()->getParent(); auto isLive = pass.liveness.isBlockLive(bb); switch (isLive) { case LiveOut: // Ignore uses within the pruned liveness. return; case LiveWithin: // Record all uses of blocks on the liveness boundary. Whoever uses this // liveness result determines the instruction-level liveness boundary to be // the last use in the block. pass.liveness.recordUser(use); break; case Dead: { // This use block has not yet been marked live. Mark it and its predecessor // blocks live. computeUseBlockLiveness(bb, pass); // Record all uses of blocks on the liveness boundary. This cannot be // determined until after the `computeUseBlockLiveness` CFG traversal // because a loop may result in this block being LiveOut. if (pass.liveness.isBlockLive(bb) == LiveWithin) pass.liveness.recordUser(use); break; } } } /// Generate pass.liveness. /// Return true if successful. /// /// This finds the "pruned" liveness by recursing through copies and ignoring /// the original destroys. /// /// Assumption: No users occur before 'def' in def's BB because this follows the /// SSA def-use chains without "looking through" any terminators. static bool computeLiveness(CopyPropagationState &pass) { assert(pass.liveness.empty()); SILBasicBlock *defBB = pass.currDef->getParentBlock(); pass.liveness.markBlockLive(defBB, LiveWithin); SmallSetVector defUseWorkList; defUseWorkList.insert(pass.currDef); while (!defUseWorkList.empty()) { SILValue value = defUseWorkList.pop_back_val(); for (Operand *use : value->getUses()) { auto *user = use->getUser(); // Bailout if we cannot yet determine the ownership of a use. if (isUnknownUse(use)) { LLVM_DEBUG(llvm::dbgs() << "Unknown owned value user: "; user->dump()); ++NumUnknownUsers; return false; } // Recurse through copies. if (auto *copy = dyn_cast(user)) { defUseWorkList.insert(copy); continue; } // An entire borrow scope is considered a single use that occurs at the // point of the end_borrow. if (auto *BBI = dyn_cast(user)) { for (Operand *use : BBI->getUses()) { if (auto *EBI = dyn_cast(use->getUser())) computeUseLiveness(use, pass); } continue; } if (isConsuming(use)) { pass.liveness.recordOriginalDestroy(use); // Destroying a values does not force liveness. if (isa(user)) continue; } computeUseLiveness(use, pass); } } return true; } //===----------------------------------------------------------------------===// // Step 3. Find the destroy points of the current def based on the pruned // liveness computed in Step 2. //===----------------------------------------------------------------------===// /// The liveness boundary is at a CFG edge `predBB` -> `succBB`, meaning that /// `pass.currDef` is live out of at least one other `predBB` successor. /// /// Create and record a final destroy_value at the beginning of `succBB` /// (assuming no critical edges). static void insertDestroyOnCFGEdge(SILBasicBlock *predBB, SILBasicBlock *succBB, CopyPropagationState &pass) { // FIXME: ban critical edges and avoid invalidating CFG analyses. auto *destroyBB = splitIfCriticalEdge(predBB, succBB); if (destroyBB != succBB) pass.markInvalid(SILAnalysis::InvalidationKind::Branches); SILBuilderWithScope B(destroyBB->begin()); auto *DI = B.createDestroyValue(succBB->begin()->getLoc(), pass.currDef); pass.destroys.recordFinalDestroy(DI); ++NumDestroysGenerated; LLVM_DEBUG(llvm::dbgs() << " Destroy on edge "; DI->dump()); pass.markInvalid(SILAnalysis::InvalidationKind::Instructions); } /// This liveness boundary is within a basic block at the given position. /// /// Create a final destroy, immediately after `pos`. static void insertDestroyAtInst(SILBasicBlock::iterator pos, CopyPropagationState &pass) { SILBuilderWithScope B(pos); auto *DI = B.createDestroyValue((*pos).getLoc(), pass.currDef); pass.destroys.recordFinalDestroy(DI); ++NumDestroysGenerated; LLVM_DEBUG(llvm::dbgs() << " Destroy at last use "; DI->dump()); pass.markInvalid(SILAnalysis::InvalidationKind::Instructions); } // The pruned liveness boundary is within the given basic block. Find the // block's last use. If the last use consumes the value, record it as a // destroy. Otherwise, insert a new destroy_value. static void findOrInsertDestroyInBlock(SILBasicBlock *bb, CopyPropagationState &pass) { auto *defInst = pass.currDef->getDefiningInstruction(); auto I = bb->getTerminator()->getIterator(); while (true) { auto *inst = &*I; Optional isConsumingResult = pass.liveness.isConsumingUser(inst); if (isConsumingResult.hasValue()) { if (isConsumingResult.getValue()) { // This consuming use becomes a final destroy. pass.destroys.recordFinalDestroy(inst); break; } // Insert a destroy after this non-consuming use. assert(inst != bb->getTerminator() && "Terminator must consume operand."); insertDestroyAtInst(std::next(I), pass); break; } // This is not a potential last user. Keep scanning. // If the original destroy is reached, this is a dead live range. Insert a // destroy immediately after the def. if (I == bb->begin()) { assert(cast(pass.currDef)->getParent() == bb); insertDestroyAtInst(I, pass); break; } --I; if (&*I == defInst) { insertDestroyAtInst(std::next(I), pass); break; } } } /// Populate `pass.finalBlockDestroys` with the final destroy points once copies /// are eliminated. This only applies to owned values. /// /// Observations: /// - The pass.currDef must be postdominated by some subset of its /// consuming uses, including destroys. /// - The postdominating consumes cannot be within nested loops. /// - Any blocks in nested loops are now marked LiveOut. static void findOrInsertDestroys(CopyPropagationState &pass) { assert(!pass.liveness.empty()); if (!pass.isValueOwned()) return; // Visit each original consuming use or destroy as the starting point for a // backward CFG traversal. for (auto *originalDestroyBB : pass.liveness.getOriginalDestroyBlocks()) { SmallSetVector blockPredWorklist; // Process each visited block, where succBB == nullptr for the starting // point of the CFG traversal. auto visitBB = [&](SILBasicBlock *bb, SILBasicBlock *succBB) { switch (pass.liveness.isBlockLive(bb)) { case LiveOut: // If succBB is null, then the original destroy must be an inner // nested destroy, so just skip it. // // Otherwise, this CFG edge is a liveness boundary, so insert a new // destroy on the edge. if (succBB) insertDestroyOnCFGEdge(bb, succBB, pass); break; case LiveWithin: // The liveness boundary is inside this block. Insert a final destroy // inside the block if it doesn't already have one. if (!pass.destroys.hasFinalDestroy(bb)) findOrInsertDestroyInBlock(bb, pass); break; case Dead: { // Continue searching upward to find the pruned liveness boundary. blockPredWorklist.insert(bb); } } }; // Perform a backward CFG traversal up to the pruned liveness boundary, // visiting each block. visitBB(originalDestroyBB, nullptr); while (!blockPredWorklist.empty()) { auto *succBB = blockPredWorklist.pop_back_val(); for (auto *predBB : succBB->getPredecessorBlocks()) visitBB(predBB, succBB); } } } //===----------------------------------------------------------------------===// // Step 4. Rewrite copies and destroys for a single copied definition. //===----------------------------------------------------------------------===// /// `pass.currDef` is live across the given consuming use. Copy the value. static void copyLiveUse(Operand *use, CopyPropagationState &pass) { SILInstruction *user = use->getUser(); SILBuilder B(user->getIterator()); B.setCurrentDebugScope(user->getDebugScope()); auto *copy = B.createCopyValue(user->getLoc(), use->get()); use->set(copy); ++NumCopiesGenerated; LLVM_DEBUG(llvm::dbgs() << " Copying at last use "; copy->dump()); pass.markInvalid(SILAnalysis::InvalidationKind::Instructions); } /// Revisit the def-use chain of `pass.currDef`. Mark unneeded original copies /// and destroys for deletion. Insert new copies for interior uses that require /// ownership of the used operand. /// /// TODO: Avoid unnecessary rewrites. Identify copies and destroys that already /// complement a non-consuming use. static void rewriteCopies(CopyPropagationState &pass) { SmallSetVector instsToDelete; SmallSetVector defUseWorklist; // Visit each operand in the def-use chain. auto visitUse = [&](Operand *use) { auto *user = use->getUser(); // Recurse through copies. if (auto *copy = dyn_cast(user)) { defUseWorklist.insert(copy); return; } if (auto *destroy = dyn_cast(user)) { // If this destroy was marked as a final destroy, ignore it; otherwise, // delete it. if (!pass.destroys.claimDestroy(destroy)) { instsToDelete.insert(destroy); LLVM_DEBUG(llvm::dbgs() << " Removing "; destroy->dump()); ++NumDestroysEliminated; } return; } // Nonconsuming uses do not need copies and cannot be marked as destroys. if (!isConsuming(use)) return; // If this use was marked as a final destroy *and* this is the first // consumed operand we have visited, then ignore it. Otherwise, treat it as // an "interior" consuming use and insert a copy. if (!pass.destroys.claimDestroy(user)) copyLiveUse(use, pass); }; // Perform a def-use traversal, visiting each use operand. defUseWorklist.insert(pass.currDef); while (!defUseWorklist.empty()) { SILValue value = defUseWorklist.pop_back_val(); // Recurse through copies then remove them. if (auto *copy = dyn_cast(value)) { for (auto *use : copy->getUses()) visitUse(use); copy->replaceAllUsesWith(copy->getOperand()); instsToDelete.insert(copy); LLVM_DEBUG(llvm::dbgs() << " Removing "; copy->dump()); ++NumCopiesEliminated; continue; } for (Operand *use : value->getUses()) visitUse(use); } assert(pass.destroys.empty()); // Remove the leftover copy_value and destroy_value instructions. if (!instsToDelete.empty()) { recursivelyDeleteTriviallyDeadInstructions(instsToDelete.takeVector(), /*force=*/true); pass.markInvalid(SILAnalysis::InvalidationKind::Instructions); } } //===----------------------------------------------------------------------===// // CopyPropagation: Top-Level Function Transform. //===----------------------------------------------------------------------===// /// TODO: we could strip casts as well, then when recursing through users keep /// track of the nearest non-copy def. For opaque values, we don't expect to see /// casts. static SILValue stripCopies(SILValue v) { while (true) { v = stripSinglePredecessorArgs(v); if (auto *srcCopy = dyn_cast(v)) { v = srcCopy->getOperand(); continue; } return v; } } namespace { class CopyPropagation : public SILFunctionTransform { /// The entry point to this function transformation. void run() override; }; } // end anonymous namespace /// Top-level pass driver. void CopyPropagation::run() { LLVM_DEBUG(llvm::dbgs() << "*** CopyPropagation: " << getFunction()->getName() << "\n"); // This algorithm fundamentally assumes ownership. if (!getFunction()->hasOwnership()) return; // Step 1. Find all copied defs. CopyPropagationState pass(getFunction()); SmallSetVector copiedDefs; for (auto &BB : *pass.F) { for (auto &I : BB) { if (auto *copy = dyn_cast(&I)) copiedDefs.insert(stripCopies(copy)); } } for (auto &def : copiedDefs) { pass.resetDef(def); // Step 2: computeLiveness if (computeLiveness(pass)) { // Step 3: findOrInsertDestroys findOrInsertDestroys(pass); // Invalidate book-keeping before deleting instructions. pass.liveness.clear(); // Step 4: rewriteCopies rewriteCopies(pass); } } invalidateAnalysis(SILAnalysis::InvalidationKind(pass.invalidation)); } SILTransform *swift::createCopyPropagation() { return new CopyPropagation(); }