Skip to content

Commit ed2e6fb

Browse files
eecksteinatrick
authored andcommitted
SILOptimizer: a new "TempLValueOpt" optimization pass for copy_addr
Optimizes copies from a temporary (an "l-value") to a destination. %temp = alloc_stack $Ty instructions_which_store_to %temp copy_addr [take] %temp to %destination dealloc_stack %temp is optimized to destroy_addr %destination instructions_which_store_to %destination The name TempLValueOpt refers to the TempRValueOpt pass, which performs a related transformation, just with the temporary on the "right" side. The TempLValueOpt is similar to CopyForwarding::backwardPropagateCopy. It's more restricted (e.g. the copy-source must be an alloc_stack). That enables other patterns to be optimized, which backwardPropagateCopy cannot handle. This pass also performs a small peephole optimization which simplifies copy_addr - destroy sequences. copy_addr %source to %destination destroy_addr %source is replace with copy_addr [take] %source to %destination (cherry picked from commit 9e92389)
1 parent 997bb9f commit ed2e6fb

File tree

6 files changed

+617
-0
lines changed

6 files changed

+617
-0
lines changed

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ PASS(PerformanceSILLinker, "performance-linker",
273273
"Deserialize all referenced SIL functions")
274274
PASS(RawSILInstLowering, "raw-sil-inst-lowering",
275275
"Lower all raw SIL instructions to canonical equivalents.")
276+
PASS(TempLValueOpt, "temp-lvalue-opt",
277+
"Remove short-lived immutable temporary l-values")
276278
PASS(TempRValueOpt, "temp-rvalue-opt",
277279
"Remove short-lived immutable temporary copies")
278280
PASS(SideEffectsDumper, "side-effects-dump",

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,9 @@ void addSSAPasses(SILPassPipelinePlan &P, OptimizationLevelKind OpLevel) {
275275
// splits up copy_addr.
276276
P.addCopyForwarding();
277277

278+
// Optimize copies from a temporary (an "l-value") to a destination.
279+
P.addTempLValueOpt();
280+
278281
// Split up opaque operations (copy_addr, retain_value, etc.).
279282
P.addLowerAggregateInstrs();
280283

lib/SILOptimizer/Transforms/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ silopt_register_sources(
3535
Sink.cpp
3636
SpeculativeDevirtualizer.cpp
3737
StackPromotion.cpp
38+
TempLValueOpt.cpp
3839
TempRValueElimination.cpp
3940
UnsafeGuaranteedPeephole.cpp
4041
)
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
//===--- TempLValueOpt.cpp - Optimize temporary l-values ------------------===//
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 copies from a temporary (an "l-value") to a destination.
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/Projection.h"
23+
#include "swift/SIL/SILBuilder.h"
24+
#include "llvm/Support/Debug.h"
25+
26+
using namespace swift;
27+
28+
namespace {
29+
30+
/// Optimizes copies from a temporary (an "l-value") to a destination.
31+
///
32+
/// \code
33+
/// %temp = alloc_stack $Ty
34+
/// instructions_which_store_to %temp
35+
/// copy_addr [take] %temp to %destination
36+
/// dealloc_stack %temp
37+
/// \endcode
38+
///
39+
/// is optimized to
40+
///
41+
/// \code
42+
/// destroy_addr %destination
43+
/// instructions_which_store_to %destination
44+
/// \endcode
45+
///
46+
/// The name TempLValueOpt refers to the TempRValueOpt pass, which performs
47+
/// a related transformation, just with the temporary on the "right" side.
48+
///
49+
/// Note that TempLValueOpt is similar to CopyForwarding::backwardPropagateCopy.
50+
/// It's more restricted (e.g. the copy-source must be an alloc_stack).
51+
/// That enables other patterns to be optimized, which backwardPropagateCopy
52+
/// cannot handle.
53+
///
54+
/// The pass also performs a peephole optimization on copy_addr - destroy
55+
/// sequences.
56+
/// It replaces
57+
///
58+
/// \code
59+
/// copy_addr %source to %destination
60+
/// destroy_addr %source
61+
/// \endcode
62+
///
63+
/// with
64+
///
65+
/// \code
66+
/// copy_addr [take] %source to %destination
67+
/// \endcode
68+
///
69+
/// This peephole optimization is also done by the DestroyHoisting pass. But
70+
/// DestroyHoisting currently only runs on OSSA.
71+
/// TODO: when DestroyHoisting can run later in the pipeline, check if we still
72+
/// need this peephole here.
73+
class TempLValueOptPass : public SILFunctionTransform {
74+
public:
75+
TempLValueOptPass() {}
76+
77+
void run() override;
78+
79+
private:
80+
AliasAnalysis *AA = nullptr;
81+
82+
bool tempLValueOpt(CopyAddrInst *copyInst);
83+
bool combineCopyAndDestroy(CopyAddrInst *copyInst);
84+
};
85+
86+
void TempLValueOptPass::run() {
87+
SILFunction *F = getFunction();
88+
if (!F->shouldOptimize())
89+
return;
90+
91+
LLVM_DEBUG(llvm::dbgs() << "*** TempLValueOptPass on function: "
92+
<< F->getName() << " ***\n");
93+
94+
AA = PM->getAnalysis<AliasAnalysis>();
95+
96+
bool changed = false;
97+
for (SILBasicBlock &block : *F) {
98+
// First collect all copy_addr instructions upfront to avoid iterator
99+
// invalidation problems (the optimizations might delete the copy_addr
100+
// itself or any following instruction).
101+
llvm::SmallVector<CopyAddrInst *, 32> copyInsts;
102+
for (SILInstruction &inst : block) {
103+
if (auto *copyInst = dyn_cast<CopyAddrInst>(&inst))
104+
copyInsts.push_back(copyInst);
105+
}
106+
// Do the optimizations.
107+
for (CopyAddrInst *copyInst : copyInsts) {
108+
changed |=combineCopyAndDestroy(copyInst);
109+
changed |=tempLValueOpt(copyInst);
110+
}
111+
}
112+
113+
if (changed) {
114+
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
115+
}
116+
}
117+
118+
static SingleValueInstruction *isMovableProjection(SILValue addr) {
119+
if (auto *enumData = dyn_cast<InitEnumDataAddrInst>(addr))
120+
return enumData;
121+
if (auto *existentialAddr = dyn_cast<InitExistentialAddrInst>(addr))
122+
return existentialAddr;
123+
124+
if (SingleValueInstruction *svi = Projection::isAddressProjection(addr)) {
125+
if (svi->getNumOperands() == 1)
126+
return svi;
127+
}
128+
return nullptr;
129+
}
130+
131+
bool TempLValueOptPass::tempLValueOpt(CopyAddrInst *copyInst) {
132+
133+
// An overview of the algorithm:
134+
//
135+
// alloc_stack %temp
136+
// ...
137+
// first_use_of %temp // beginOfLiferange
138+
// ... // no reads or writes from/to %destination
139+
// copy_addr [take] %temp to %destination // copyInst
140+
// ... // no further uses of %temp (copyInst is the end of %temp liferange)
141+
// dealloc_stack %temp
142+
//
143+
// All projections to %destination are hoisted above the first use of %temp.
144+
// Then all uses of %temp are replaced by %destination.
145+
// In case the copyInst is not initializing %destination, a
146+
// 'destroy_addr %destination' is inserted before the first use of %temp.
147+
148+
if (!copyInst->isTakeOfSrc())
149+
return false;
150+
151+
auto *temporary = dyn_cast<AllocStackInst>(copyInst->getSrc());
152+
if (!temporary)
153+
return false;
154+
155+
// Collect all users of the temporary into a set. Also, for simplicity,
156+
// require that all users are within a single basic block.
157+
SmallPtrSet<SILInstruction *, 8> users;
158+
SILBasicBlock *block = temporary->getParent();
159+
for (Operand *use : temporary->getUses()) {
160+
SILInstruction *user = use->getUser();
161+
if (user->getParent() != block)
162+
return false;
163+
users.insert(user);
164+
}
165+
166+
// Collect all address projections of the destination - in case we need to
167+
// hoist them.
168+
SmallPtrSet<SILInstruction *, 4> projections;
169+
SILValue destination = copyInst->getDest();
170+
SILValue destRootAddr = destination;
171+
while (SingleValueInstruction *projInst = isMovableProjection(destRootAddr)) {
172+
// In theory, users of the temporary and address projections of the
173+
// destination should be completely distinct. Otherwise the copyInst would
174+
// be an identity copy (source == destination).
175+
// Just to be on the safe side, bail if it's not the case (instead of an
176+
// assert).
177+
if (users.count(projInst))
178+
return false;
179+
projections.insert(projInst);
180+
destRootAddr = projInst->getOperand(0);
181+
}
182+
// The root address of the destination. It's null if it's not an instruction,
183+
// but a block argument.
184+
SILInstruction *destRootInst = destRootAddr->getDefiningInstruction();
185+
186+
// Iterate over the liferange of the temporary and make some validity checks.
187+
SILInstruction *beginOfLiferange = nullptr;
188+
bool endOfLiferangeReached = false;
189+
for (auto iter = temporary->getIterator(); iter != block->end(); ++iter) {
190+
SILInstruction *inst = &*iter;
191+
// The dealloc_stack is the last user of the temporary.
192+
if (isa<DeallocStackInst>(inst) && inst->getOperand(0) == temporary)
193+
break;
194+
195+
if (users.count(inst) != 0) {
196+
// Check if the copyInst is the last user of the temporary (beside the
197+
// dealloc_stack).
198+
if (endOfLiferangeReached)
199+
return false;
200+
201+
// Find the first user of the temporary to get a more precise liferange.
202+
// It would be too conservative to treat the alloc_stack itself as the
203+
// begin of the liferange.
204+
if (!beginOfLiferange)
205+
beginOfLiferange = inst;
206+
207+
if (inst == copyInst)
208+
endOfLiferangeReached = true;
209+
}
210+
if (beginOfLiferange && !endOfLiferangeReached) {
211+
// If the root address of the destination is within the liferange of the
212+
// temporary, we cannot replace all uses of the temporary with the
213+
// destination (it would break the def-use dominance rule).
214+
if (inst == destRootInst)
215+
return false;
216+
217+
// Check if the destination is not accessed within the liferange of
218+
// the temporary.
219+
// This is unlikely, because the destination is initialized at the
220+
// copyInst. But still, the destination could contain an initialized value
221+
// which is destroyed before the copyInst.
222+
if (AA->mayReadOrWriteMemory(inst, destination) &&
223+
// Needed to treat init_existential_addr as not-writing projection.
224+
projections.count(inst) == 0)
225+
return false;
226+
}
227+
}
228+
assert(endOfLiferangeReached);
229+
230+
// Move all projections of the destination address before the liferange of
231+
// the temporary.
232+
for (auto iter = beginOfLiferange->getIterator();
233+
iter != copyInst->getIterator();) {
234+
SILInstruction *inst = &*iter++;
235+
if (projections.count(inst))
236+
inst->moveBefore(beginOfLiferange);
237+
}
238+
239+
if (!copyInst->isInitializationOfDest()) {
240+
// Make sure the the destination is uninitialized before the liferange of
241+
// the temporary.
242+
SILBuilderWithScope builder(beginOfLiferange);
243+
builder.createDestroyAddr(copyInst->getLoc(), destination);
244+
}
245+
246+
// Replace all uses of the temporary with the destination address.
247+
while (!temporary->use_empty()) {
248+
Operand *use = *temporary->use_begin();
249+
SILInstruction *user = use->getUser();
250+
switch (user->getKind()) {
251+
case SILInstructionKind::DeallocStackInst:
252+
user->eraseFromParent();
253+
break;
254+
default:
255+
use->set(destination);
256+
}
257+
}
258+
temporary->eraseFromParent();
259+
copyInst->eraseFromParent();
260+
return true;
261+
}
262+
263+
bool TempLValueOptPass::combineCopyAndDestroy(CopyAddrInst *copyInst) {
264+
if (copyInst->isTakeOfSrc())
265+
return false;
266+
267+
// Find a destroy_addr of the copy's source address.
268+
DestroyAddrInst *destroy = nullptr;
269+
for (Operand *srcUse : copyInst->getSrc()->getUses()) {
270+
if ((destroy = dyn_cast<DestroyAddrInst>(srcUse->getUser())))
271+
break;
272+
}
273+
SILBasicBlock *block = copyInst->getParent();
274+
if (!destroy || destroy->getParent() != block)
275+
return false;
276+
assert(destroy->getOperand() == copyInst->getSrc());
277+
278+
// Check if the destroy_addr is after the copy_addr and if there are no
279+
// memory accesses between them.
280+
for (auto iter = std::next(copyInst->getIterator());
281+
iter != block->end(); ++iter) {
282+
SILInstruction *inst = &*iter;
283+
if (inst == destroy) {
284+
copyInst->setIsTakeOfSrc(IsTake);
285+
destroy->eraseFromParent();
286+
return true;
287+
}
288+
if (inst->mayReadOrWriteMemory())
289+
return false;
290+
}
291+
return false;
292+
}
293+
294+
} // end anonymous namespace
295+
296+
SILTransform *swift::createTempLValueOpt() {
297+
return new TempLValueOptPass();
298+
}
299+

0 commit comments

Comments
 (0)