-
Notifications
You must be signed in to change notification settings - Fork 10.4k
/
Copy pathTempLValueOpt.cpp
361 lines (323 loc) · 12.3 KB
/
TempLValueOpt.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
//===--- TempLValueOpt.cpp - Optimize temporary l-values ------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This pass optimizes copies from a temporary (an "l-value") to a destination.
//
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "cow-opts"
#include "swift/Basic/Assertions.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Analysis/AliasAnalysis.h"
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
#include "swift/SIL/NodeBits.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILBasicBlock.h"
#include "swift/SIL/Projection.h"
#include "swift/SIL/SILBuilder.h"
#include "llvm/Support/Debug.h"
using namespace swift;
namespace {
/// Optimizes copies from a temporary (an "l-value") to a destination.
///
/// \code
/// %temp = alloc_stack $Ty
/// instructions_which_store_to %temp
/// copy_addr [take] %temp to %destination
/// dealloc_stack %temp
/// \endcode
///
/// is optimized to
///
/// \code
/// destroy_addr %destination
/// instructions_which_store_to %destination
/// \endcode
///
/// The name TempLValueOpt refers to the TempRValueOpt pass, which performs
/// a related transformation, just with the temporary on the "right" side.
///
/// Note that 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.
///
/// The pass also performs a peephole optimization on copy_addr - destroy
/// sequences.
/// It replaces
///
/// \code
/// copy_addr %source to %destination
/// destroy_addr %source
/// \endcode
///
/// with
///
/// \code
/// copy_addr [take] %source to %destination
/// \endcode
///
/// This peephole optimization is also done by the DestroyHoisting pass. But
/// DestroyHoisting currently only runs on OSSA.
/// TODO: when DestroyHoisting can run later in the pipeline, check if we still
/// need this peephole here.
class TempLValueOptPass : public SILFunctionTransform {
public:
TempLValueOptPass() {}
void run() override;
private:
void tempLValueOpt(CopyAddrInst *copyInst);
void combineCopyAndDestroy(CopyAddrInst *copyInst);
bool hasInvalidApplyArgumentAliasing(SILInstruction *inst, SILValue tempAddr,
SILValue destAddr, AliasAnalysis *aa);
};
void TempLValueOptPass::run() {
SILFunction *F = getFunction();
if (!F->shouldOptimize())
return;
LLVM_DEBUG(llvm::dbgs() << "*** TempLValueOptPass on function: "
<< F->getName() << " ***\n");
for (SILBasicBlock &block : *F) {
// First collect all copy_addr instructions upfront to avoid iterator
// invalidation problems (the optimizations might delete the copy_addr
// itself or any following instruction).
llvm::SmallVector<CopyAddrInst *, 32> copyInsts;
for (SILInstruction &inst : block) {
if (auto *copyInst = dyn_cast<CopyAddrInst>(&inst))
copyInsts.push_back(copyInst);
}
// Do the optimizations.
for (CopyAddrInst *copyInst : copyInsts) {
combineCopyAndDestroy(copyInst);
tempLValueOpt(copyInst);
}
}
}
static SingleValueInstruction *isMovableProjection(SILValue addr) {
if (auto *existentialAddr = dyn_cast<InitExistentialAddrInst>(addr))
return existentialAddr;
if (SingleValueInstruction *svi = Projection::isAddressProjection(addr)) {
if (svi->getNumOperands() == 1)
return svi;
}
return nullptr;
}
void TempLValueOptPass::tempLValueOpt(CopyAddrInst *copyInst) {
// An overview of the algorithm:
//
// alloc_stack %temp
// ...
// first_use_of %temp // beginOfLiverange
// ... // no reads or writes from/to %destination
// copy_addr [take] %temp to %destination // copyInst
// ... // no further uses of %temp (copyInst is the end of %temp liverange)
// dealloc_stack %temp
//
// All projections to %destination are hoisted above the first use of %temp.
// Then all uses of %temp are replaced by %destination.
// In case the copyInst is not initializing %destination, a
// 'destroy_addr %destination' is inserted before the first use of %temp.
if (!copyInst->isTakeOfSrc())
return;
auto *temporary = dyn_cast<AllocStackInst>(copyInst->getSrc());
if (!temporary)
return;
// Collect all users of the temporary into a set. Also, for simplicity,
// require that all users are within a single basic block.
InstructionSet users(getFunction());
SILBasicBlock *block = temporary->getParent();
for (Operand *use : temporary->getUses()) {
SILInstruction *user = use->getUser();
if (user->getParent() != block)
return;
users.insert(user);
}
// Collect all address projections of the destination - in case we need to
// hoist them.
InstructionSet projections(getFunction());
SILValue destination = copyInst->getDest();
SILValue destRootAddr = destination;
while (SingleValueInstruction *projInst = isMovableProjection(destRootAddr)) {
// In theory, users of the temporary and address projections of the
// destination should be completely distinct. Otherwise the copyInst would
// be an identity copy (source == destination).
// Just to be on the safe side, bail if it's not the case (instead of an
// assert).
if (users.contains(projInst))
return;
projections.insert(projInst);
destRootAddr = projInst->getOperand(0);
}
// The root address of the destination. It's null if it's not an instruction,
// but a block argument.
SILInstruction *destRootInst = destRootAddr->getDefiningInstruction();
bool needDestroyEarly = false;
BasicCalleeAnalysis *bca = nullptr;
if (!copyInst->isInitializationOfDest()) {
needDestroyEarly = true;
bca = PM->getAnalysis<BasicCalleeAnalysis>();
}
// Iterate over the liverange of the temporary and make some validity checks.
AliasAnalysis *AA = nullptr;
SILInstruction *beginOfLiverange = nullptr;
bool endOfLiverangeReached = false;
for (auto iter = temporary->getIterator(); iter != block->end(); ++iter) {
SILInstruction *inst = &*iter;
// The dealloc_stack is the last user of the temporary.
if (isa<DeallocStackInst>(inst) && inst->getOperand(0) == temporary)
break;
if (users.contains(inst) != 0) {
// Check if the copyInst is the last user of the temporary (beside the
// dealloc_stack).
if (endOfLiverangeReached)
return;
// Find the first user of the temporary to get a more precise liverange.
// It would be too conservative to treat the alloc_stack itself as the
// begin of the liverange.
if (!beginOfLiverange)
beginOfLiverange = inst;
if (inst == copyInst)
endOfLiverangeReached = true;
}
if (beginOfLiverange && !endOfLiverangeReached) {
// If the root address of the destination is within the liverange of the
// temporary, we cannot replace all uses of the temporary with the
// destination (it would break the def-use dominance rule).
if (inst == destRootInst)
return;
if (!AA)
AA = PM->getAnalysis<AliasAnalysis>(getFunction());
// Check if the destination is not accessed within the liverange of
// the temporary.
// This is unlikely, because the destination is initialized at the
// copyInst. But still, the destination could contain an initialized value
// which is destroyed before the copyInst.
if (AA->mayReadOrWriteMemory(inst, destination) &&
// Needed to treat init_existential_addr as not-writing projection.
projections.contains(inst) == 0)
return;
// Check if replacing the temporary with destination would invalidate an applied
// function's alias rules of indirect arguments.
if (hasInvalidApplyArgumentAliasing(inst, temporary, destination, AA))
return;
if (needDestroyEarly && isDeinitBarrier(inst, bca))
return;
}
}
assert(endOfLiverangeReached);
// Move all projections of the destination address before the liverange of
// the temporary.
for (auto iter = beginOfLiverange->getIterator();
iter != copyInst->getIterator();) {
SILInstruction *inst = &*iter++;
if (projections.contains(inst))
inst->moveBefore(beginOfLiverange);
}
if (!copyInst->isInitializationOfDest()) {
// Make sure the destination is uninitialized before the liverange of
// the temporary.
SILBuilderWithScope builder(beginOfLiverange);
builder.createDestroyAddr(copyInst->getLoc(), destination);
}
// Replace all uses of the temporary with the destination address.
while (!temporary->use_empty()) {
Operand *use = *temporary->use_begin();
SILInstruction *user = use->getUser();
switch (user->getKind()) {
case SILInstructionKind::DeallocStackInst:
user->eraseFromParent();
break;
default:
use->set(destination);
}
}
temporary->eraseFromParent();
copyInst->eraseFromParent();
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}
/// Returns true if after replacing tempAddr with destAddr an apply instruction
/// would have invalid aliasing of indirect arguments.
///
/// An indirect argument (except `@inout_aliasable`) must not alias with another
/// indirect argument. Now, if we would replace tempAddr with destAddr in
///
/// apply %f(%tempAddr, %destAddr) : (@in T) -> @out T
///
/// we would invalidate this rule.
/// This is even true if the called function does not read from destAddr.
///
bool TempLValueOptPass::hasInvalidApplyArgumentAliasing(SILInstruction *inst,
SILValue tempAddr,
SILValue destAddr,
AliasAnalysis *aa) {
auto as = FullApplySite::isa(inst);
if (!as)
return false;
bool tempAccessed = false;
bool destAccessed = false;
bool mutatingAccess = false;
for (Operand &argOp : as.getArgumentOperands()) {
auto conv = as.getArgumentConvention(argOp);
if (conv.isExclusiveIndirectParameter()) {
if (aa->mayAlias(tempAddr, argOp.get())) {
tempAccessed = true;
if (!conv.isGuaranteedConventionInCaller())
mutatingAccess = true;
} else if (aa->mayAlias(destAddr, argOp.get())) {
destAccessed = true;
if (!conv.isGuaranteedConventionInCaller())
mutatingAccess = true;
}
}
}
return mutatingAccess && tempAccessed && destAccessed;
}
void TempLValueOptPass::combineCopyAndDestroy(CopyAddrInst *copyInst) {
if (copyInst->isTakeOfSrc())
return;
// Find a destroy_addr of the copy's source address.
DestroyAddrInst *destroy = nullptr;
for (Operand *srcUse : copyInst->getSrc()->getUses()) {
if ((destroy = dyn_cast<DestroyAddrInst>(srcUse->getUser())))
break;
}
SILBasicBlock *block = copyInst->getParent();
if (!destroy || destroy->getParent() != block)
return;
assert(destroy->getOperand() == copyInst->getSrc());
// Check if the destroy_addr is after the copy_addr and if there are no
// memory accesses between them.
SmallVector<SILInstruction *, 4> debugInsts;
for (auto iter = std::next(copyInst->getIterator());
iter != block->end(); ++iter) {
SILInstruction *inst = &*iter;
if (inst == destroy) {
copyInst->setIsTakeOfSrc(IsTake);
destroy->eraseFromParent();
// Cleanup all debug_value of the copy src btw the copy and destroy
for (auto debugInst : debugInsts) {
debugInst->eraseFromParent();
}
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
return;
}
if(auto *debugInst = DebugValueInst::hasAddrVal(inst)) {
if (debugInst->getOperand() == copyInst->getSrc())
debugInsts.push_back(debugInst);
}
if (inst->mayReadOrWriteMemory())
return;
}
}
} // end anonymous namespace
SILTransform *swift::createTempLValueOpt() {
return new TempLValueOptPass();
}