|
| 1 | +//===--- CanonicalOSSALifetime.h - Canonicalize OSSA lifetimes --*- C++ -*-===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift.org open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2014 - 2020 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 | +/// Canonicalize the copies and destroys of a single owned or guaranteed OSSA |
| 14 | +/// value. |
| 15 | +/// |
| 16 | +/// This top-level API rewrites the extended OSSA lifetime of a SILValue: |
| 17 | +/// |
| 18 | +/// void canonicalizeValueLifetime(SILValue def, CanonicalOSSALifetime &) |
| 19 | +/// |
| 20 | +/// The extended lifetime transitively includes the uses of `def` itself along |
| 21 | +/// with the uses of any copies of `def`. Canonicalization provably minimizes |
| 22 | +/// the OSSA lifetime and its copies by rewriting all copies and destroys. Only |
| 23 | +/// consusming uses that are not on the liveness boundary require a copy. |
| 24 | +/// |
| 25 | +/// Example #1: Handle consuming and nonconsuming uses. |
| 26 | +/// |
| 27 | +/// bb0(%arg : @owned $T, %addr : @trivial $*T): |
| 28 | +/// %copy = copy_value %arg : $T |
| 29 | +/// debug_value %copy : $T |
| 30 | +/// store %copy to [init] %addr : $*T |
| 31 | +/// debug_value %arg : $T |
| 32 | +/// debug_value_addr %addr : $*T |
| 33 | +/// destroy_value %arg : $T |
| 34 | +/// |
| 35 | +/// Will be transformed to: |
| 36 | +/// |
| 37 | +/// bb0(%arg : @owned $T, %addr : @trivial $*T): |
| 38 | +/// // The original copy is deleted. |
| 39 | +/// debug_value %arg : $T |
| 40 | +/// // A new copy_value is inserted before the consuming store. |
| 41 | +/// %copy = copy_value %arg : $T |
| 42 | +/// store %copy to [init] %addr : $*T |
| 43 | +/// // The non-consuming use now uses the original value. |
| 44 | +/// debug_value %arg : $T |
| 45 | +/// // A new destroy is inserted after the last use. |
| 46 | +/// destroy_value %arg : $T |
| 47 | +/// debug_value_addr %addr : $*T |
| 48 | +/// // The original destroy is deleted. |
| 49 | +/// |
| 50 | +/// Example #2: Handle control flow. |
| 51 | +/// |
| 52 | +/// bb0(%arg : @owned $T): |
| 53 | +/// cond_br %_, bb1, bb2 |
| 54 | +/// bb1: |
| 55 | +/// br bb3 |
| 56 | +/// bb2: |
| 57 | +/// debug_value %arg : $T |
| 58 | +/// %copy = copy_value %arg : $T |
| 59 | +/// destroy_value %copy : $T |
| 60 | +/// br bb3 |
| 61 | +/// bb3: |
| 62 | +/// destroy_value %arg : $T |
| 63 | +/// |
| 64 | +/// Will be transformed to: |
| 65 | +/// |
| 66 | +/// bb0(%arg : @owned $T): |
| 67 | +/// cond_br %_, bb1, bb2 |
| 68 | +/// bb1: |
| 69 | +/// destroy_value %arg : $T |
| 70 | +/// br bb3 |
| 71 | +/// bb2: |
| 72 | +/// // The original copy is deleted. |
| 73 | +/// debug_value %arg : $T |
| 74 | +/// destroy_value %arg : $T |
| 75 | +/// br bb3 |
| 76 | +/// bb2: |
| 77 | +/// // The original destroy is deleted. |
| 78 | +/// |
| 79 | +/// FIXME: Canonicalization currently bails out if any uses of the def has |
| 80 | +/// OperandOwnership::PointerEscape. Once project_box is protected by a borrow |
| 81 | +/// scope and mark_dependence is associated with an end_dependence, |
| 82 | +/// canonicalization will work everywhere as intended. The intention is to keep |
| 83 | +/// the canonicalization algorithm as simple and robust, leaving the remaining |
| 84 | +/// performance opportunities contingent on fixing the SIL representation. |
| 85 | +/// |
| 86 | +/// FIXME: Canonicalization currently fails to eliminate copies downstream of a |
| 87 | +/// ForwardingBorrow. Aggregates should be fixed to be Reborrow instead of |
| 88 | +/// ForwardingBorrow, then computeCanonicalLiveness() can be fixed to extend |
| 89 | +/// liveness through ForwardingBorrows. |
| 90 | +/// |
| 91 | +//===----------------------------------------------------------------------===// |
| 92 | + |
| 93 | +#ifndef SWIFT_SILOPTIMIZER_UTILS_CANONICALOSSALIFETIME_H |
| 94 | +#define SWIFT_SILOPTIMIZER_UTILS_CANONICALOSSALIFETIME_H |
| 95 | + |
| 96 | +#include "llvm/ADT/DenseMap.h" |
| 97 | +#include "llvm/ADT/SetVector.h" |
| 98 | +#include "swift/SIL/SILInstruction.h" |
| 99 | +#include "swift/SILOptimizer/Utils/PrunedLiveness.h" |
| 100 | + |
| 101 | +namespace swift { |
| 102 | + |
| 103 | +/// Information about consumes on the extended-lifetime boundary. Consuming uses |
| 104 | +/// within the lifetime are not included--they will consume a copy after |
| 105 | +/// rewriting. For borrowed def values, the consumes do not include the end of |
| 106 | +/// the borrow scope, rather the consumes transitively include consumes of any |
| 107 | +/// owned copies of the borrowed value. |
| 108 | +/// |
| 109 | +/// This result remains valid during copy rewriting. The only instructions |
| 110 | +/// referenced it contains are consumes that cannot be deleted. |
| 111 | +class CanonicalOSSAConsumeInfo { |
| 112 | + /// Map blocks on the lifetime boundary to the last consuming instruction. |
| 113 | + llvm::SmallDenseMap<SILBasicBlock *, SILInstruction *, 4> finalBlockConsumes; |
| 114 | + |
| 115 | + /// Record any debug_value instructions found after a final consume. |
| 116 | + SmallVector<DebugValueInst *, 8> debugAfterConsume; |
| 117 | + |
| 118 | + /// For borrowed defs, track per-block copies of the borrowed value that only |
| 119 | + /// have uses outside the borrow scope and will not be removed by |
| 120 | + /// canonicalization. These copies are effectively distinct OSSA lifetimes |
| 121 | + /// that should be canonicalized separately. |
| 122 | + llvm::SmallDenseMap<SILBasicBlock *, CopyValueInst *, 4> persistentCopies; |
| 123 | + |
| 124 | +public: |
| 125 | + bool hasUnclaimedConsumes() const { return !finalBlockConsumes.empty(); } |
| 126 | + |
| 127 | + void clear() { |
| 128 | + finalBlockConsumes.clear(); |
| 129 | + debugAfterConsume.clear(); |
| 130 | + persistentCopies.clear(); |
| 131 | + } |
| 132 | + |
| 133 | + bool hasFinalConsumes() const { return !finalBlockConsumes.empty(); } |
| 134 | + |
| 135 | + void recordFinalConsume(SILInstruction *inst) { |
| 136 | + assert(!finalBlockConsumes.count(inst->getParent())); |
| 137 | + finalBlockConsumes[inst->getParent()] = inst; |
| 138 | + } |
| 139 | + |
| 140 | + // Return true if this instruction is marked as a final consume point of the |
| 141 | + // current def's live range. A consuming instruction can only be claimed once |
| 142 | + // because instructions like `tuple` can consume the same value via multiple |
| 143 | + // operands. |
| 144 | + bool claimConsume(SILInstruction *inst) { |
| 145 | + auto destroyPos = finalBlockConsumes.find(inst->getParent()); |
| 146 | + if (destroyPos != finalBlockConsumes.end() && destroyPos->second == inst) { |
| 147 | + finalBlockConsumes.erase(destroyPos); |
| 148 | + return true; |
| 149 | + } |
| 150 | + return false; |
| 151 | + } |
| 152 | + |
| 153 | + /// Record a debug_value that is known to be outside pruned liveness. Assumes |
| 154 | + /// that instructions are only visited once. |
| 155 | + void recordDebugAfterConsume(DebugValueInst *dvi) { |
| 156 | + debugAfterConsume.push_back(dvi); |
| 157 | + } |
| 158 | + |
| 159 | + ArrayRef<DebugValueInst *> getDebugInstsAfterConsume() const { |
| 160 | + return debugAfterConsume; |
| 161 | + } |
| 162 | + |
| 163 | + bool hasPersistentCopies() const { return !persistentCopies.empty(); } |
| 164 | + |
| 165 | + bool isPersistentCopy(CopyValueInst *copy) const { |
| 166 | + auto iter = persistentCopies.find(copy->getParent()); |
| 167 | + if (iter == persistentCopies.end()) { |
| 168 | + return false; |
| 169 | + } |
| 170 | + return iter->second == copy; |
| 171 | + } |
| 172 | + |
| 173 | + SWIFT_ASSERT_ONLY_DECL(void dump() const LLVM_ATTRIBUTE_USED); |
| 174 | +}; |
| 175 | + |
| 176 | +/// Canonicalize OSSA lifetimes. |
| 177 | +/// |
| 178 | +/// Allows the allocation of analysis state to be reused across calls to |
| 179 | +/// canonicalizeValueLifetime(). |
| 180 | +class CanonicalizeOSSALifetime { |
| 181 | +public: |
| 182 | + /// Find the original definition of a potentially copied value. |
| 183 | + static SILValue getCanonicalCopiedDef(SILValue v); |
| 184 | + |
| 185 | +private: |
| 186 | + /// If true, then debug_value instructions outside of non-debug |
| 187 | + /// liveness may be pruned during canonicalization. |
| 188 | + bool pruneDebug; |
| 189 | + |
| 190 | + /// Current copied def for which this state describes the liveness. |
| 191 | + SILValue currentDef; |
| 192 | + |
| 193 | + /// If an outer copy is created for uses outside the borrow scope |
| 194 | + CopyValueInst *outerCopy = nullptr; |
| 195 | + |
| 196 | + /// Cumulatively, have any instructions been modified by canonicalization? |
| 197 | + bool changed = false; |
| 198 | + |
| 199 | + /// Original points in the CFG where the current value's lifetime is consumed |
| 200 | + /// or destroyed. For guaranteed values it remains empty. A backward walk from |
| 201 | + /// these blocks must discover all uses on paths that lead to a return or |
| 202 | + /// throw. |
| 203 | + /// |
| 204 | + /// These blocks are not necessarily in the pruned live blocks since |
| 205 | + /// pruned liveness does not consider destroy_values. |
| 206 | + SmallSetVector<SILBasicBlock *, 8> consumingBlocks; |
| 207 | + |
| 208 | + /// Record all interesting debug_value instructions here rather then treating |
| 209 | + /// them like a normal use. An interesting debug_value is one that may lie |
| 210 | + /// outisde the pruned liveness at the time it is discovered. |
| 211 | + llvm::SmallDenseSet<DebugValueInst *, 8> debugValues; |
| 212 | + |
| 213 | + /// Reuse a general worklist for def-use traversal. |
| 214 | + SmallSetVector<SILValue, 8> defUseWorklist; |
| 215 | + |
| 216 | + /// Reuse a general worklist for CFG traversal. |
| 217 | + SmallSetVector<SILBasicBlock *, 8> blockWorklist; |
| 218 | + |
| 219 | + /// Pruned liveness for the extended live range including copies. For this |
| 220 | + /// purpose, only consuming instructions are considered "lifetime |
| 221 | + /// ending". end_borrows do not end a liverange that may include owned copies. |
| 222 | + PrunedLiveness liveness; |
| 223 | + |
| 224 | + /// Information about consuming instructions discovered in this caonical OSSA |
| 225 | + /// lifetime. |
| 226 | + CanonicalOSSAConsumeInfo consumes; |
| 227 | + |
| 228 | +public: |
| 229 | + CanonicalizeOSSALifetime(bool pruneDebug) : pruneDebug(pruneDebug) {} |
| 230 | + |
| 231 | + SILValue getCurrentDef() const { return currentDef; } |
| 232 | + |
| 233 | + void initDef(SILValue def) { |
| 234 | + assert(consumingBlocks.empty() && debugValues.empty() && liveness.empty()); |
| 235 | + consumes.clear(); |
| 236 | + |
| 237 | + currentDef = def; |
| 238 | + outerCopy = nullptr; |
| 239 | + liveness.initializeDefBlock(def->getParentBlock()); |
| 240 | + } |
| 241 | + |
| 242 | + void clearLiveness() { |
| 243 | + consumingBlocks.clear(); |
| 244 | + debugValues.clear(); |
| 245 | + liveness.clear(); |
| 246 | + } |
| 247 | + |
| 248 | + bool hasChanged() const { return changed; } |
| 249 | + |
| 250 | + void setChanged() { changed = true; } |
| 251 | + |
| 252 | + SILValue createdOuterCopy() const { return outerCopy; } |
| 253 | + |
| 254 | + /// Top-Level API: rewrites copies and destroys within \p def's extended |
| 255 | + /// lifetime. \p lifetime caches transient analysis state across multiple |
| 256 | + /// calls. |
| 257 | + /// |
| 258 | + /// Return false if the OSSA structure cannot be recognized (with a proper |
| 259 | + /// OSSA representation this will always return true). |
| 260 | + /// |
| 261 | + /// Upon returning, isChanged() indicates, cumulatively, whether any SIL |
| 262 | + /// changes were made. |
| 263 | + /// |
| 264 | + /// Upon returning, createdOuterCopy() indicates whether a new copy was |
| 265 | + /// created for uses outside the borrow scope. To canonicalize the new outer |
| 266 | + /// lifetime, call this API again on the value defined by the new copy. |
| 267 | + bool canonicalizeValueLifetime(SILValue def); |
| 268 | + |
| 269 | +protected: |
| 270 | + void recordDebugValue(DebugValueInst *dvi) { |
| 271 | + debugValues.insert(dvi); |
| 272 | + } |
| 273 | + |
| 274 | + void recordConsumingUse(Operand *use) { |
| 275 | + consumingBlocks.insert(use->getUser()->getParent()); |
| 276 | + } |
| 277 | + |
| 278 | + bool computeBorrowLiveness(); |
| 279 | + |
| 280 | + void consolidateBorrowScope(); |
| 281 | + |
| 282 | + bool computeCanonicalLiveness(); |
| 283 | + |
| 284 | + void findOrInsertDestroyInBlock(SILBasicBlock *bb); |
| 285 | + |
| 286 | + void findOrInsertDestroys(); |
| 287 | + |
| 288 | + void rewriteCopies(); |
| 289 | +}; |
| 290 | + |
| 291 | +} // end namespace swift |
| 292 | + |
| 293 | +#endif |
0 commit comments