|
| 1 | +//===--- OSSALifetimeCompletion.cpp ---------------------------------------===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift.org open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See https://swift.org/LICENSE.txt for license information |
| 9 | +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 10 | +// |
| 11 | +//===----------------------------------------------------------------------===// |
| 12 | +/// |
| 13 | +/// OSSA lifetime completion adds lifetime ending instructions to make |
| 14 | +/// linear lifetimes complete. |
| 15 | +/// |
| 16 | +/// Interior liveness handles the following cases naturally: |
| 17 | +/// |
| 18 | +/// When completing the lifetime if the initial value, %v1, transitively |
| 19 | +/// include all dominated reborrows. %phi1 in this example: |
| 20 | +/// |
| 21 | +/// %v1 = ... |
| 22 | +/// cond_br bb1, bb2 |
| 23 | +/// bb1: |
| 24 | +/// %b1 = begin_borrow %v1 |
| 25 | +/// br bb3(%b1) |
| 26 | +/// bb2: |
| 27 | +/// %b2 = begin_borrow %v1 |
| 28 | +/// br bb3(%b2) |
| 29 | +/// bb3(%phi1): |
| 30 | +/// %u1 = %phi1 |
| 31 | +/// end_borrow %phi1 |
| 32 | +/// %k1 = destroy_value %v1 // must be below end_borrow %phi1 |
| 33 | +/// |
| 34 | +/// When completing the lifetime for a (%phi2) transitively include all inner |
| 35 | +/// adjacent reborrows (%phi1): |
| 36 | +/// |
| 37 | +/// bb1: |
| 38 | +/// %v1 = ... |
| 39 | +/// %b1 = begin_borrow %v1 |
| 40 | +/// br bb3(%b1, %v1) |
| 41 | +/// bb2: |
| 42 | +/// %v2 = ... |
| 43 | +/// %b2 = begin_borrow %v2 |
| 44 | +/// br bb3(%b2, %v2) |
| 45 | +/// bb3(%phi1, %phi2): |
| 46 | +/// %u1 = %phi1 |
| 47 | +/// end_borrow %phi1 |
| 48 | +/// %k1 = destroy_value %phi1 |
| 49 | +/// |
| 50 | +//===----------------------------------------------------------------------===// |
| 51 | + |
| 52 | +#include "swift/SIL/OSSALifetimeCompletion.h" |
| 53 | +#include "swift/SIL/SILBuilder.h" |
| 54 | +#include "swift/SIL/SILInstruction.h" |
| 55 | + |
| 56 | +using namespace swift; |
| 57 | + |
| 58 | +static SILInstruction *endOSSALifetime(SILValue value, SILBuilder &builder) { |
| 59 | + auto loc = |
| 60 | + RegularLocation::getAutoGeneratedLocation(builder.getInsertionPointLoc()); |
| 61 | + if (value->getOwnershipKind() == OwnershipKind::Owned) { |
| 62 | + return builder.createDestroyValue(loc, value); |
| 63 | + } |
| 64 | + return builder.createEndBorrow(loc, value); |
| 65 | +} |
| 66 | + |
| 67 | +static bool endLifetimeAtBoundary(SILValue value, |
| 68 | + const SSAPrunedLiveness &liveness) { |
| 69 | + PrunedLivenessBoundary boundary; |
| 70 | + liveness.computeBoundary(boundary); |
| 71 | + |
| 72 | + bool changed = false; |
| 73 | + for (SILInstruction *lastUser : boundary.lastUsers) { |
| 74 | + if (liveness.isInterestingUser(lastUser) |
| 75 | + != PrunedLiveness::LifetimeEndingUse) { |
| 76 | + changed = true; |
| 77 | + SILBuilderWithScope::insertAfter(lastUser, [value](SILBuilder &builder) { |
| 78 | + endOSSALifetime(value, builder); |
| 79 | + }); |
| 80 | + } |
| 81 | + } |
| 82 | + for (SILBasicBlock *edge : boundary.boundaryEdges) { |
| 83 | + changed = true; |
| 84 | + SILBuilderWithScope builder(edge->begin()); |
| 85 | + endOSSALifetime(value, builder); |
| 86 | + } |
| 87 | + for (SILNode *deadDef : boundary.deadDefs) { |
| 88 | + SILInstruction *next = nullptr; |
| 89 | + if (auto *deadInst = dyn_cast<SILInstruction>(deadDef)) { |
| 90 | + next = deadInst->getNextInstruction(); |
| 91 | + } else { |
| 92 | + next = cast<ValueBase>(deadDef)->getNextInstruction(); |
| 93 | + } |
| 94 | + changed = true; |
| 95 | + SILBuilderWithScope builder(next); |
| 96 | + endOSSALifetime(value, builder); |
| 97 | + } |
| 98 | + return changed; |
| 99 | +} |
| 100 | + |
| 101 | +static bool endLifetimeAtUnreachableBlocks(SILValue value, |
| 102 | + const SSAPrunedLiveness &liveness) { |
| 103 | + PrunedLivenessBoundary boundary; |
| 104 | + liveness.computeBoundary(boundary); |
| 105 | + |
| 106 | + BasicBlockWorklist deadEndBlocks(value->getFunction()); |
| 107 | + for (SILInstruction *lastUser : boundary.lastUsers) { |
| 108 | + if (liveness.isInterestingUser(lastUser) |
| 109 | + != PrunedLiveness::LifetimeEndingUse) { |
| 110 | + deadEndBlocks.push(lastUser->getParent()); |
| 111 | + } |
| 112 | + } |
| 113 | + for (SILBasicBlock *edge : boundary.boundaryEdges) { |
| 114 | + deadEndBlocks.push(edge); |
| 115 | + } |
| 116 | + for (SILNode *deadDef : boundary.deadDefs) { |
| 117 | + deadEndBlocks.push(deadDef->getParentBlock()); |
| 118 | + } |
| 119 | + // Forward CFG walk from the non-lifetime-ending boundary to the unreachable |
| 120 | + // instructions. |
| 121 | + bool changed = false; |
| 122 | + while (auto *block = deadEndBlocks.pop()) { |
| 123 | + if (block->succ_empty()) { |
| 124 | + // This assert will fail unless there are already lifetime-ending |
| 125 | + // instruction on all paths to normal function exits. |
| 126 | + auto *unreachable = cast<UnreachableInst>(block->getTerminator()); |
| 127 | + SILBuilderWithScope builder(unreachable); |
| 128 | + endOSSALifetime(value, builder); |
| 129 | + changed = true; |
| 130 | + } |
| 131 | + for (auto *successor : block->getSuccessorBlocks()) { |
| 132 | + deadEndBlocks.push(successor); |
| 133 | + } |
| 134 | + } |
| 135 | + return changed; |
| 136 | +} |
| 137 | + |
| 138 | +/// End the lifetime of \p value at unreachable instructions. |
| 139 | +/// |
| 140 | +/// Returns true if any new instructions were created to complete the lifetime. |
| 141 | +/// |
| 142 | +/// This is only meant to cleanup lifetimes that lead to dead-end blocks. After |
| 143 | +/// recursively completing all nested scopes, it then simply ends the lifetime |
| 144 | +/// at the Unreachable instruction. |
| 145 | +bool OSSALifetimeCompletion::analyzeAndUpdateLifetime(SILValue value) { |
| 146 | + // Called for inner borrows, inner adjacent reborrows, inner reborrows, and |
| 147 | + // scoped addresses. |
| 148 | + auto handleInnerScope = [this](SILValue innerBorrowedValue) { |
| 149 | + completeOSSALifetime(innerBorrowedValue); |
| 150 | + }; |
| 151 | + InteriorLiveness liveness(value); |
| 152 | + liveness.compute(domInfo, handleInnerScope); |
| 153 | + |
| 154 | + bool changed = false; |
| 155 | + if (value->isLexical()) { |
| 156 | + changed |= endLifetimeAtUnreachableBlocks(value, liveness.getLiveness()); |
| 157 | + } else { |
| 158 | + changed |= endLifetimeAtBoundary(value, liveness.getLiveness()); |
| 159 | + } |
| 160 | + // TODO: Rebuild outer adjacent phis on demand (SILGen does not currently |
| 161 | + // produce guaranteed phis). See FindEnclosingDefs & |
| 162 | + // findSuccessorDefsFromPredDefs. If no enclosing phi is found, we can create |
| 163 | + // it here and use updateSSA to recursively populate phis. |
| 164 | + assert(liveness.getUnenclosedPhis().empty()); |
| 165 | + return changed; |
| 166 | +} |
| 167 | + |
| 168 | +// TODO: create a fast check for 'mayEndLifetime(SILInstruction *)'. Verify that |
| 169 | +// it returns true for every instruction that has a lifetime-ending operand. |
| 170 | +void UnreachableLifetimeCompletion::visitUnreachableInst( |
| 171 | + SILInstruction *instruction) { |
| 172 | + auto *block = instruction->getParent(); |
| 173 | + bool inReachableBlock = !unreachableBlocks.contains(block); |
| 174 | + // If this instruction's block is already marked unreachable, and |
| 175 | + // updatingLifetimes is not yet set, then this instruction will be visited |
| 176 | + // again later when propagating unreachable blocks. |
| 177 | + if (!inReachableBlock && !updatingLifetimes) |
| 178 | + return; |
| 179 | + |
| 180 | + for (Operand &operand : instruction->getAllOperands()) { |
| 181 | + if (!operand.isLifetimeEnding()) |
| 182 | + continue; |
| 183 | + |
| 184 | + SILValue value = operand.get(); |
| 185 | + SILBasicBlock *defBlock = value->getParentBlock(); |
| 186 | + if (unreachableBlocks.contains(defBlock)) |
| 187 | + continue; |
| 188 | + |
| 189 | + auto *def = value->getDefiningInstruction(); |
| 190 | + if (def && unreachableInsts.contains(def)) |
| 191 | + continue; |
| 192 | + |
| 193 | + // The operand's definition is still reachable and its lifetime ends on a |
| 194 | + // newly unreachable path. |
| 195 | + // |
| 196 | + // Note: The arguments of a no-return try_apply may still appear reachable |
| 197 | + // here because the try_apply itself is never visited as unreachable, hence |
| 198 | + // its successor blocks are not marked . But it |
| 199 | + // seems harmless to recompute their lifetimes. |
| 200 | + |
| 201 | + // Insert this unreachable instruction in unreachableInsts if its parent |
| 202 | + // block is not already marked unreachable. |
| 203 | + if (inReachableBlock) { |
| 204 | + unreachableInsts.insert(instruction); |
| 205 | + } |
| 206 | + incompleteValues.insert(value); |
| 207 | + |
| 208 | + // Add unreachable successors to the forward traversal worklist. |
| 209 | + if (auto *term = dyn_cast<TermInst>(instruction)) { |
| 210 | + for (auto *succBlock : term->getSuccessorBlocks()) { |
| 211 | + if (llvm::all_of(succBlock->getPredecessorBlocks(), |
| 212 | + [&](SILBasicBlock *predBlock) { |
| 213 | + if (predBlock == block) |
| 214 | + return true; |
| 215 | + |
| 216 | + return unreachableBlocks.contains(predBlock); |
| 217 | + })) { |
| 218 | + unreachableBlocks.insert(succBlock); |
| 219 | + } |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | +} |
| 224 | + |
| 225 | +bool UnreachableLifetimeCompletion::completeLifetimes() { |
| 226 | + assert(!updatingLifetimes && "don't call this more than once"); |
| 227 | + updatingLifetimes = true; |
| 228 | + |
| 229 | + // Now that all unreachable terminator instructions have been visited, |
| 230 | + // propagate unreachable blocks. |
| 231 | + for (auto blockIt = unreachableBlocks.begin(); |
| 232 | + blockIt != unreachableBlocks.end(); ++blockIt) { |
| 233 | + auto *block = *blockIt; |
| 234 | + for (auto &instruction : *block) { |
| 235 | + visitUnreachableInst(&instruction); |
| 236 | + } |
| 237 | + } |
| 238 | + |
| 239 | + OSSALifetimeCompletion completion(function, domInfo); |
| 240 | + |
| 241 | + bool changed = false; |
| 242 | + for (auto value : incompleteValues) { |
| 243 | + if (completion.completeOSSALifetime(value) |
| 244 | + == LifetimeCompletion::WasCompleted) { |
| 245 | + changed = true; |
| 246 | + } |
| 247 | + } |
| 248 | + return changed; |
| 249 | +} |
| 250 | + |
0 commit comments