|
| 1 | +//===--- COWOpts.cpp - Optimize COW operations ----------------------------===// |
| 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 | +// This pass optimizes begin_cow_mutation and end_cow_mutation patterns. |
| 14 | +// |
| 15 | +//===----------------------------------------------------------------------===// |
| 16 | + |
| 17 | +#define DEBUG_TYPE "cow-opts" |
| 18 | +#include "swift/SILOptimizer/PassManager/Transforms.h" |
| 19 | +#include "swift/SILOptimizer/Analysis/AliasAnalysis.h" |
| 20 | +#include "swift/SIL/SILFunction.h" |
| 21 | +#include "swift/SIL/SILBasicBlock.h" |
| 22 | +#include "swift/SIL/SILArgument.h" |
| 23 | +#include "swift/SIL/SILBuilder.h" |
| 24 | +#include "llvm/Support/Debug.h" |
| 25 | + |
| 26 | +using namespace swift; |
| 27 | + |
| 28 | +namespace { |
| 29 | + |
| 30 | +/// Constant folds the uniqueness result of begin_cow_mutation instructions. |
| 31 | +/// |
| 32 | +/// If it can be proved that the buffer argument is uniquely referenced, the |
| 33 | +/// uniqueness result is replaced with a constant boolean "true". |
| 34 | +/// For example: |
| 35 | +/// |
| 36 | +/// \code |
| 37 | +/// %buffer = end_cow_mutation %mutable_buffer |
| 38 | +/// // ... |
| 39 | +/// // %buffer does not escape here |
| 40 | +/// // ... |
| 41 | +/// (%is_unique, %mutable_buffer2) = begin_cow_mutation %buffer |
| 42 | +/// cond_br %is_unique, ... |
| 43 | +/// \endcode |
| 44 | +/// |
| 45 | +/// is replaced with |
| 46 | +/// |
| 47 | +/// \code |
| 48 | +/// %buffer = end_cow_mutation [keep_unique] %mutable_buffer |
| 49 | +/// // ... |
| 50 | +/// (%not_used, %mutable_buffer2) = begin_cow_mutation %buffer |
| 51 | +/// %true = integer_literal 1 |
| 52 | +/// cond_br %true, ... |
| 53 | +/// \endcode |
| 54 | +/// |
| 55 | +/// Note that the keep_unique flag is set on the end_cow_mutation because the |
| 56 | +/// code now relies on that the buffer is really uniquely referenced. |
| 57 | +/// |
| 58 | +/// The optimization can also handle def-use chains between end_cow_mutation and |
| 59 | +/// begin_cow_mutation which involve phi-arguments. |
| 60 | +/// |
| 61 | +/// An additional peephole optimization is performed: if the begin_cow_mutation |
| 62 | +/// is the only use of the end_cow_mutation, the whole pair of instructions |
| 63 | +/// is eliminated. |
| 64 | +/// |
| 65 | +class COWOptsPass : public SILFunctionTransform { |
| 66 | +public: |
| 67 | + COWOptsPass() {} |
| 68 | + |
| 69 | + void run() override; |
| 70 | + |
| 71 | +private: |
| 72 | + using InstructionSet = SmallPtrSet<SILInstruction *, 8>; |
| 73 | + using VoidPointerSet = SmallPtrSet<void *, 8>; |
| 74 | + |
| 75 | + AliasAnalysis *AA = nullptr; |
| 76 | + |
| 77 | + bool optimizeBeginCOW(BeginCOWMutationInst *BCM); |
| 78 | + |
| 79 | + static void collectEscapePoints(SILValue v, |
| 80 | + InstructionSet &escapePoints, |
| 81 | + VoidPointerSet &handled); |
| 82 | +}; |
| 83 | + |
| 84 | +void COWOptsPass::run() { |
| 85 | + SILFunction *F = getFunction(); |
| 86 | + if (!F->shouldOptimize()) |
| 87 | + return; |
| 88 | + |
| 89 | + LLVM_DEBUG(llvm::dbgs() << "*** RedundantPhiElimination on function: " |
| 90 | + << F->getName() << " ***\n"); |
| 91 | + |
| 92 | + AA = PM->getAnalysis<AliasAnalysis>(); |
| 93 | + |
| 94 | + bool changed = false; |
| 95 | + for (SILBasicBlock &block : *F) { |
| 96 | + auto iter = block.begin(); |
| 97 | + while (iter != block.end()) { |
| 98 | + SILInstruction *inst = &*iter++; |
| 99 | + if (auto *beginCOW = dyn_cast<BeginCOWMutationInst>(inst)) |
| 100 | + changed |= optimizeBeginCOW(beginCOW); |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + if (changed) { |
| 105 | + invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +bool COWOptsPass::optimizeBeginCOW(BeginCOWMutationInst *BCM) { |
| 110 | + VoidPointerSet handled; |
| 111 | + SmallVector<SILValue, 8> workList; |
| 112 | + SmallPtrSet<EndCOWMutationInst *, 4> endCOWMutationInsts; |
| 113 | + |
| 114 | + // Collect all end_cow_mutation instructions, used by the begin_cow_mutation, |
| 115 | + // looking through block phi-arguments. |
| 116 | + workList.push_back(BCM->getOperand()); |
| 117 | + while (!workList.empty()) { |
| 118 | + SILValue v = workList.pop_back_val(); |
| 119 | + if (SILPhiArgument *arg = dyn_cast<SILPhiArgument>(v)) { |
| 120 | + if (handled.insert(arg).second) { |
| 121 | + SmallVector<SILValue, 4> incomingVals; |
| 122 | + if (!arg->getIncomingPhiValues(incomingVals)) |
| 123 | + return false; |
| 124 | + for (SILValue incomingVal : incomingVals) { |
| 125 | + workList.push_back(incomingVal); |
| 126 | + } |
| 127 | + } |
| 128 | + } else if (auto *ECM = dyn_cast<EndCOWMutationInst>(v)) { |
| 129 | + endCOWMutationInsts.insert(ECM); |
| 130 | + } else { |
| 131 | + return false; |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + // Collect all uses of the end_cow_instructions, where the buffer can |
| 136 | + // potentially escape. |
| 137 | + handled.clear(); |
| 138 | + InstructionSet potentialEscapePoints; |
| 139 | + for (EndCOWMutationInst *ECM : endCOWMutationInsts) { |
| 140 | + collectEscapePoints(ECM, potentialEscapePoints, handled); |
| 141 | + } |
| 142 | + |
| 143 | + if (!potentialEscapePoints.empty()) { |
| 144 | + // Now, this is the complicated part: check if there is an escape point |
| 145 | + // within the liverange between the end_cow_mutation(s) and |
| 146 | + // begin_cow_mutation. |
| 147 | + // |
| 148 | + // For store instructions we do a little bit more: only count a store as an |
| 149 | + // escape if there is a (potential) load from the same address within the |
| 150 | + // liverange. |
| 151 | + handled.clear(); |
| 152 | + SmallVector<SILInstruction *, 8> instWorkList; |
| 153 | + SmallVector<SILInstruction *, 8> potentialLoadInsts; |
| 154 | + llvm::DenseSet<SILValue> storeAddrs; |
| 155 | + |
| 156 | + // This is a simple worklist-based backward dataflow analysis. |
| 157 | + // Start at the initial begin_cow_mutation and go backward. |
| 158 | + instWorkList.push_back(BCM); |
| 159 | + |
| 160 | + while (!instWorkList.empty()) { |
| 161 | + SILInstruction *inst = instWorkList.pop_back_val(); |
| 162 | + for (;;) { |
| 163 | + if (potentialEscapePoints.count(inst) != 0) { |
| 164 | + if (auto *store = dyn_cast<StoreInst>(inst)) { |
| 165 | + // Don't immediately bail on a store instruction. Instead, remember |
| 166 | + // it and check if it interfers with any (potential) load. |
| 167 | + storeAddrs.insert(store->getDest()); |
| 168 | + } else { |
| 169 | + return false; |
| 170 | + } |
| 171 | + } |
| 172 | + if (inst->mayReadFromMemory()) |
| 173 | + potentialLoadInsts.push_back(inst); |
| 174 | + |
| 175 | + // An end_cow_mutation marks the begin of the liverange. It's the end |
| 176 | + // point of the dataflow analysis. |
| 177 | + auto *ECM = dyn_cast<EndCOWMutationInst>(inst); |
| 178 | + if (ECM && endCOWMutationInsts.count(ECM) != 0) |
| 179 | + break; |
| 180 | + |
| 181 | + if (inst == &inst->getParent()->front()) { |
| 182 | + for (SILBasicBlock *pred : inst->getParent()->getPredecessorBlocks()) { |
| 183 | + if (handled.insert(pred).second) |
| 184 | + instWorkList.push_back(pred->getTerminator()); |
| 185 | + } |
| 186 | + break; |
| 187 | + } |
| 188 | + |
| 189 | + inst = &*std::prev(inst->getIterator()); |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + // Check if there is any (potential) load from a memory location where the |
| 194 | + // buffer is stored to. |
| 195 | + if (!storeAddrs.empty()) { |
| 196 | + // Avoid quadratic behavior. Usually this limit is not exceeded. |
| 197 | + if (storeAddrs.size() * potentialLoadInsts.size() > 128) |
| 198 | + return false; |
| 199 | + for (SILInstruction *load : potentialLoadInsts) { |
| 200 | + for (SILValue storeAddr : storeAddrs) { |
| 201 | + if (!AA || AA->mayReadFromMemory(load, storeAddr)) |
| 202 | + return false; |
| 203 | + } |
| 204 | + } |
| 205 | + } |
| 206 | + } |
| 207 | + |
| 208 | + // Replace the uniqueness result of the begin_cow_mutation with an integer |
| 209 | + // literal of "true". |
| 210 | + SILBuilderWithScope B(BCM); |
| 211 | + auto *IL = B.createIntegerLiteral(BCM->getLoc(), |
| 212 | + BCM->getUniquenessResult()->getType(), 1); |
| 213 | + BCM->getUniquenessResult()->replaceAllUsesWith(IL); |
| 214 | + |
| 215 | + // Try the peephole optimization: remove an end_cow_mutation/begin_cow_mutation |
| 216 | + // pair completely if the begin_cow_mutation is the only use of |
| 217 | + // end_cow_mutation. |
| 218 | + if (auto *singleEndCOW = dyn_cast<EndCOWMutationInst>(BCM->getOperand())) { |
| 219 | + assert(endCOWMutationInsts.size() == 1 && |
| 220 | + *endCOWMutationInsts.begin() == singleEndCOW); |
| 221 | + if (singleEndCOW->hasOneUse()) { |
| 222 | + BCM->getBufferResult()->replaceAllUsesWith(singleEndCOW->getOperand()); |
| 223 | + BCM->eraseFromParent(); |
| 224 | + singleEndCOW->eraseFromParent(); |
| 225 | + return true; |
| 226 | + } |
| 227 | + } |
| 228 | + |
| 229 | + for (EndCOWMutationInst *ECM : endCOWMutationInsts) { |
| 230 | + // This is important for other optimizations: The code is now relying on |
| 231 | + // the buffer to be unique. |
| 232 | + ECM->setKeepUnique(); |
| 233 | + } |
| 234 | + |
| 235 | + return true; |
| 236 | +} |
| 237 | + |
| 238 | +void COWOptsPass::collectEscapePoints(SILValue v, |
| 239 | + InstructionSet &escapePoints, |
| 240 | + VoidPointerSet &handled) { |
| 241 | + if (!handled.insert(v.getOpaqueValue()).second) |
| 242 | + return; |
| 243 | + |
| 244 | + for (Operand *use : v->getUses()) { |
| 245 | + SILInstruction *user = use->getUser(); |
| 246 | + switch (user->getKind()) { |
| 247 | + case SILInstructionKind::BeginCOWMutationInst: |
| 248 | + case SILInstructionKind::RefElementAddrInst: |
| 249 | + case SILInstructionKind::RefTailAddrInst: |
| 250 | + break; |
| 251 | + case SILInstructionKind::BranchInst: |
| 252 | + collectEscapePoints(cast<BranchInst>(user)->getArgForOperand(use), |
| 253 | + escapePoints, handled); |
| 254 | + break; |
| 255 | + case SILInstructionKind::CondBranchInst: |
| 256 | + collectEscapePoints(cast<CondBranchInst>(user)->getArgForOperand(use), |
| 257 | + escapePoints, handled); |
| 258 | + break; |
| 259 | + case SILInstructionKind::StructInst: |
| 260 | + case SILInstructionKind::TupleInst: |
| 261 | + case SILInstructionKind::UncheckedRefCastInst: |
| 262 | + collectEscapePoints(cast<SingleValueInstruction>(user), |
| 263 | + escapePoints, handled); |
| 264 | + break; |
| 265 | + default: |
| 266 | + // Everything else is considered to be a potential escape of the buffer. |
| 267 | + escapePoints.insert(user); |
| 268 | + } |
| 269 | + } |
| 270 | +} |
| 271 | + |
| 272 | +} // end anonymous namespace |
| 273 | + |
| 274 | +SILTransform *swift::createCOWOpts() { |
| 275 | + return new COWOptsPass(); |
| 276 | +} |
| 277 | + |
0 commit comments