-
Notifications
You must be signed in to change notification settings - Fork 10.4k
/
Copy pathInOutDeshadowing.cpp
321 lines (266 loc) · 11 KB
/
InOutDeshadowing.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
//===--- InOutDeshadowing.cpp - Remove non-escaping inout shadows ---------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// SILGen produces shadow variables for "inout" arguments to provide proper
// semantics for when the inout argument is closed over. However, this shadow
// value is *only* needed when the argument is closed over (and when that
// closure isn't inlined). This pass looks for shadow allocations and removes
// them.
//
// This is a guaranteed optimization pass, because adding additional references
// can cause algorithmic performance changes, e.g. turning amortized constant
// time string and array operations into linear time operations.
//
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "inout-deshadow"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Support/Debug.h"
using namespace swift;
STATISTIC(NumShadowsRemoved, "Number of inout shadow variables removed");
STATISTIC(NumShadowsKept, "Number of inout shadow variables kept");
//===----------------------------------------------------------------------===//
// inout Deshadowing
//===----------------------------------------------------------------------===//
/// promoteShadow - Given an AllocStackInst that is copied to/from an inout
/// argument, completely replace the alloc_stack with that inout argument.
static void promoteShadow(AllocStackInst *Alloc, SILArgument *InOutArg) {
// Since the allocation has already been promoted to an alloc_stack, we know
// it doesn't escape. Simply eliminate the allocation and any obviously
// trivial noop copies into and out of it.
while (!Alloc->use_empty()) {
auto Use = *Alloc->use_begin();
auto *User = Use->getUser();
// If this is the dealloc_stack, just zap the instruction.
if (isa<DeallocStackInst>(User)) {
User->eraseFromParent();
continue;
}
// Otherwise, it is a use of the argument. If this is a copy_addr that
// defines or destroys the value, then remove it.
if (auto *CAI = dyn_cast<CopyAddrInst>(User)) {
if (CAI->getSrc() == InOutArg || CAI->getDest() == InOutArg) {
User->eraseFromParent();
continue;
}
}
// Otherwise, this is something else that is using the memory. Remap this
// to use the InOutArg directly instead of using the allocation.
Use->set(InOutArg);
}
// Make the debug information stored in the AllocBox explicit.
SILBuilder B(Alloc);
B.createDebugValueAddr(Alloc->getLoc(), InOutArg, Alloc->getVarInfo());
Alloc->eraseFromParent();
}
//===----------------------------------------------------------------------===//
// Candidate Variable Identification
//===----------------------------------------------------------------------===//
class StackSlotState {
bool Failed : 1;
bool HaveEntrySlot : 1;
AllocStackInst *TheSlot = nullptr;
llvm::SmallPtrSet<SILBasicBlock*, 1> ExitBBs;
public:
StackSlotState(SILFunction *F)
: Failed(false), HaveEntrySlot(false)
{
// We need to see a store back to the inout on every exit path.
for (auto &bb : *F) {
auto term = bb.getTerminator();
if (term->isFunctionExiting()) {
DEBUG(llvm::dbgs() << " need load from stack slot on exit " << &bb
<< '\n');
ExitBBs.insert(&bb);
}
}
}
/// True if analysis has concluded that deshadowing cannot happen.
bool failed() {
return Failed;
}
/// Get the single stack slot we can deshadow, or null if no such slot was
/// found.
AllocStackInst *getDeshadowableSlot() {
if (Failed)
return nullptr;
// We must have seen both a store to and a load from the slot to deshadow
// on every exit BB.
if (!HaveEntrySlot) {
DEBUG(llvm::dbgs() << "*** Rejecting deshadow: no store to stack slot\n");
return nullptr;
}
if (!ExitBBs.empty()) {
DEBUG(llvm::dbgs() << "*** Rejecting deshadow: no load from stack slot "
"on some paths\n");
return nullptr;
}
return TheSlot;
}
/// Add a stack slot that was copy-initialized from the inout on entry
/// to the analysis. If we already have seen a load, or if the slot does not
/// match the exit slot, the analysis fails.
void addEntrySlot(AllocStackInst *slot) {
if (Failed) return;
if (HaveEntrySlot)
return setFailed("inout is loaded multiple times");
if (TheSlot && slot != TheSlot)
return setFailed("inout is loaded and stored into different slots");
DEBUG(llvm::dbgs() << " found store to stack slot on entry\n");
HaveEntrySlot = true;
TheSlot = slot;
}
/// Add a stack slot that was take-assigned to the inout from an exit BB
/// to the analysis. If we already have seen a store on this BB, or if the
/// slot does not match the entry slot, the analysis fails.
void addExitSlot(AllocStackInst *slot, SILBasicBlock *exitBB) {
if (Failed) return;
if (TheSlot && slot != TheSlot)
return setFailed("inout is loaded and stored into different slots");
if (!ExitBBs.erase(exitBB))
return setFailed("inout is stored multiple times from same exit BB");
DEBUG(llvm::dbgs() << " found load from stack slot on exit "
<< exitBB << '\n');
TheSlot = slot;
}
/// Fail the analysis.
void setFailed(StringRef reason) {
DEBUG(llvm::dbgs() << "*** Rejecting deshadow: " << reason << '\n');
Failed = true;
}
};
/// isCopyToOrFromStack - Check to see if the specified use of an inout
/// argument is a copy_addr to/from an alloc_stack.
///
/// This returns the alloc_stack if found, or null if not.
static void analyzeUseOfInOut(Operand *UI, StackSlotState &state) {
// Look for copy_addr instructions.
auto CAI = dyn_cast<CopyAddrInst>(UI->getUser());
// Non-copy_addr uses of the inout should only occur in canonical SIL, so
// fail the analysis if we see one.
if (!CAI)
return state.setFailed("inout is used by a non-copy_addr instruction");
// We only look at autogenerated copy_addr's. We don't want to muck with
// user variables, as in:
// func f(inout a : Int) { var b = a }
if (!CAI->getLoc().isAutoGenerated() && !CAI->getLoc().isSILFile())
return;
// Get a stack slot, looking through mark_uninitialized if necessary.
auto getStackSlot = [](SILValue V) {
// Look through mark_uninitialized.
if (auto *MUI = dyn_cast<MarkUninitializedInst>(V))
V = MUI->getOperand();
return dyn_cast<AllocStackInst>(V);
};
// Are we the source or destination?
switch (UI->getOperandNumber()) {
case 0: { // Source
// Is this copy in the entry block?
if (CAI->getParent()->getIterator() != CAI->getFunction()->begin())
// Any copy from the inout outside of the entry block fails the analysis.
// We don't need full flow-sensitive analysis for SILGen-ed code.
return state.setFailed("inout is loaded outside of the entry block");
// Fail if this isn't a copy-initialization.
if (CAI->isTakeOfSrc() || !CAI->isInitializationOfDest())
return state.setFailed("inout is loaded as a non-copy-initialization");
// Fail if this isn't a store to a stack slot.
AllocStackInst *destSlot = getStackSlot(CAI->getDest());
if (!destSlot)
return state.setFailed("inout is loaded to a non-stack slot");
// Add the entry slot to the analysis.
return state.addEntrySlot(destSlot);
}
case 1: { // Destination
// Is this copy in an exit block?
auto term = CAI->getParent()->getTerminator();
// Unreachable blocks don't matter to the analysis.
if (isa<UnreachableInst>(term))
return;
if (!term->isFunctionExiting())
// Any copy from the inout outside of an exit block fails the analysis.
// We don't need full flow-sensitive analysis for SILGen-ed code.
return state.setFailed("inout is stored outside of an exit block");
// Fail if this isn't an assignment.
if (CAI->isInitializationOfDest())
return state.setFailed("inout is stored as a non-assignment");
// Fail if this isn't a load from a stack slot.
AllocStackInst *srcSlot = getStackSlot(CAI->getSrc());
if (!srcSlot)
return state.setFailed("inout is stored from a non-stack slot");
// Add the exit slot to the analysis.
return state.addExitSlot(srcSlot, CAI->getParent());
}
default:
llvm_unreachable("copy_addr only has two operands");
}
}
/// processInOutValue - Walk the use-def list of the inout argument to find uses
/// of it. If we find any autogenerated copies to/from an alloc_stack, then
/// remove the alloc stack in favor of loading/storing to the inout pointer
/// directly.
///
/// This returns true if it promotes away the shadow variable.
///
static bool processInOutValue(SILArgument *InOutArg) {
assert(InOutArg->getType().isAddress() &&
"inout arguments should always be addresses");
{
StackSlotState state(InOutArg->getFunction());
for (auto UI : InOutArg->getUses()) {
analyzeUseOfInOut(UI, state);
if (state.failed())
goto failed;
}
AllocStackInst *stackSlot = state.getDeshadowableSlot();
if (!stackSlot)
goto failed;
DEBUG(llvm::dbgs() << " Promoting shadow variable " << *stackSlot);
promoteShadow(stackSlot, InOutArg);
return true;
}
failed:
// If we fail, dump out some internal state.
DEBUG({
llvm::dbgs() << "*** Failed to deshadow. Uses:\n";
for (auto UI : InOutArg->getUses())
llvm::dbgs() << " " << *UI->getUser();
});
return false;
}
namespace {
class InOutDeshadowing : public SILFunctionTransform {
/// The entry point to the transformation.
void run() override {
SILFunction &F = *getFunction();
// For each function, find any inout arguments and try to optimize each of
// them.
SILFunctionType *FTI = F.getLoweredFunctionType();
for (unsigned arg = 0, e = FTI->getParameters().size();
arg != e; ++arg) {
if (!FTI->getParameters()[arg].isIndirectInOut()) continue;
DEBUG(llvm::dbgs()<< " " << F.getName() << ": argument #"<< arg << "\n");
if (processInOutValue(F.getArgumentsWithoutIndirectResults()[arg]))
++NumShadowsRemoved;
else {
++NumShadowsKept;
}
}
}
StringRef getName() override { return "InOut Deshadowing"; }
};
} // end anonymous namespace
SILTransform *swift::createInOutDeshadowing() {
return new InOutDeshadowing();
}