Skip to content

Commit 9722578

Browse files
committed
SILOptimizer: a new optimization for copy-on-write
Constant folds the uniqueness result of begin_cow_mutation instructions, if it can be proved that the buffer argument is uniquely referenced. For example: %buffer = end_cow_mutation %mutable_buffer // ... // %buffer does not escape here // ... (%is_unique, %mutable_buffer2) = begin_cow_mutation %buffer cond_br %is_unique, ... is replaced with %buffer = end_cow_mutation [keep_unique] %mutable_buffer // ... (%not_used, %mutable_buffer2) = begin_cow_mutation %buffer %true = integer_literal 1 cond_br %true, ... Note that the keep_unique flag is set on the end_cow_mutation because the code now relies on that the buffer is really uniquely referenced. The optimization can also handle def-use chains between end_cow_mutation and begin_cow_mutation which involve phi-arguments. An additional peephole optimization is performed: if the begin_cow_mutation is the only use of the end_cow_mutation, the whole pair of instructions is eliminated.
1 parent 889e84a commit 9722578

File tree

5 files changed

+440
-0
lines changed

5 files changed

+440
-0
lines changed

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ PASS(CopyForwarding, "copy-forwarding",
120120
"Copy Forwarding to Remove Redundant Copies")
121121
PASS(CopyPropagation, "copy-propagation",
122122
"Copy propagation to Remove Redundant SSA Copies")
123+
PASS(COWOpts, "cow-opts",
124+
"Optimize COW operations")
123125
PASS(Differentiation, "differentiation",
124126
"Automatic Differentiation")
125127
PASS(EpilogueARCMatcherDumper, "sil-epilogue-arc-dumper",

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ void addFunctionPasses(SILPassPipelinePlan &P,
366366
P.addRedundantLoadElimination();
367367
}
368368

369+
P.addCOWOpts();
369370
P.addPerformanceConstantPropagation();
370371
// Remove redundant arguments right before CSE and DCE, so that CSE and DCE
371372
// can cleanup redundant and dead instructions.
@@ -595,6 +596,7 @@ static void addLateLoopOptPassPipeline(SILPassPipelinePlan &P) {
595596
P.addAccessEnforcementReleaseSinking();
596597
P.addAccessEnforcementOpts();
597598
P.addLICM();
599+
P.addCOWOpts();
598600
// Simplify CFG after LICM that creates new exit blocks
599601
P.addSimplifyCFG();
600602
// LICM might have added new merging potential by hoisting

lib/SILOptimizer/Transforms/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ target_sources(swiftSILOptimizer PRIVATE
88
ArrayCountPropagation.cpp
99
ArrayElementValuePropagation.cpp
1010
AssumeSingleThreaded.cpp
11+
COWOpts.cpp
1112
CSE.cpp
1213
ConditionForwarding.cpp
1314
CopyForwarding.cpp
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
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

Comments
 (0)