Skip to content

Commit 4a2565b

Browse files
committed
[ShrinkBorrowScope] Adopt iterative dataflow.
Adopt IterativeBackwardReachability and VisitBarrierAccessScopes. Enables hoisting borrow scopes over loops and avoiding hoisting borrow scopes into unrelated access scopes, respectively. rdar://93186505 rdar://93060369
1 parent ca70a23 commit 4a2565b

File tree

3 files changed

+122
-88
lines changed

3 files changed

+122
-88
lines changed

include/swift/SILOptimizer/Analysis/Reachability.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,6 @@
1313
/// Reachability data flow analysis using a path-discovery worklist. For
1414
/// efficient data flow propagation based on a single SSA value and its uses.
1515
///
16-
/// TODO: Add an optimistic data flow for more aggresive optimization:
17-
/// - Add another set for blocks reachable by barriers
18-
/// - Change the meet operation to a union
19-
/// - Propagate past barriers up to the SSA def
20-
/// - Iterate to a fix-point.
21-
///
2216
//===----------------------------------------------------------------------===//
2317

2418
#ifndef SWIFT_SILOPTIMIZER_ANALYSIS_REACHABILITY_H

lib/SILOptimizer/Utils/ShrinkBorrowScope.cpp

Lines changed: 120 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "swift/SIL/SILBasicBlock.h"
2222
#include "swift/SIL/SILInstruction.h"
2323
#include "swift/SILOptimizer/Analysis/Reachability.h"
24+
#include "swift/SILOptimizer/Analysis/VisitBarrierAccessScopes.h"
2425
#include "swift/SILOptimizer/Utils/CanonicalizeBorrowScope.h"
2526
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
2627
#include "swift/SILOptimizer/Utils/InstructionDeleter.h"
@@ -49,6 +50,8 @@ struct Context final {
4950
/// introducer->getOperand()
5051
SILValue const borrowee;
5152

53+
SILBasicBlock *defBlock;
54+
5255
SILFunction &function;
5356

5457
/// The copy_value instructions that the utility creates or changes.
@@ -62,7 +65,8 @@ struct Context final {
6265
SmallVectorImpl<CopyValueInst *> &modifiedCopyValueInsts,
6366
InstructionDeleter &deleter)
6467
: introducer(introducer), borrowedValue(BorrowedValue(&introducer)),
65-
borrowee(introducer.getOperand()), function(*introducer.getFunction()),
68+
borrowee(introducer.getOperand()), defBlock(introducer.getParent()),
69+
function(*introducer.getFunction()),
6670
modifiedCopyValueInsts(modifiedCopyValueInsts), deleter(deleter) {}
6771
Context(Context const &) = delete;
6872
Context &operator=(Context const &) = delete;
@@ -75,7 +79,7 @@ struct Usage final {
7579
SmallPtrSet<SILInstruction *, 16> users;
7680
// The instructions from which the shrinking starts, the scope ending
7781
// instructions.
78-
llvm::SmallSetVector<SILInstruction *, 4> ends;
82+
llvm::SmallVector<SILInstruction *, 4> ends;
7983

8084
Usage(){};
8185
Usage(Usage const &) = delete;
@@ -95,7 +99,7 @@ bool findUsage(Context const &context, Usage &usage) {
9599
// If a scope ending instruction is not an end_borrow, bail out.
96100
if (!isa<EndBorrowInst>(instruction))
97101
return false;
98-
usage.ends.insert(instruction);
102+
usage.ends.push_back(instruction);
99103
}
100104

101105
SmallVector<Operand *, 16> uses;
@@ -112,81 +116,94 @@ bool findUsage(Context const &context, Usage &usage) {
112116

113117
/// How end_borrow hoisting is obstructed.
114118
struct DeinitBarriers final {
115-
/// Blocks up to "before the beginning" of which hoisting was able to proceed.
116-
BasicBlockSetVector hoistingReachesBeginBlocks;
117-
118-
/// Blocks to "after the end" of which hoisting was able to proceed.
119-
BasicBlockSet hoistingReachesEndBlocks;
120-
121119
/// Copies to be rewritten as copies of %borrowee.
122120
SmallVector<CopyValueInst *, 4> copies;
123121

124122
/// Instructions above which end_borrows cannot be hoisted.
125-
SmallVector<SILInstruction *, 4> barriers;
123+
SmallSetVector<SILInstruction *, 4> instructions;
126124

127125
/// Blocks one of whose phis is a barrier and consequently out of which
128126
/// end_borrows cannot be hoisted.
129-
SmallVector<SILBasicBlock *, 4> phiBarriers;
127+
SmallSetVector<SILBasicBlock *, 4> phis;
130128

131-
DeinitBarriers(Context &context)
132-
: hoistingReachesBeginBlocks(&context.function),
133-
hoistingReachesEndBlocks(&context.function) {}
129+
/// Blocks whose single predecessors has another successor to the top of which
130+
/// end_borrows cannot be hoisted.
131+
SmallSetVector<SILBasicBlock *, 4> blocks;
132+
133+
DeinitBarriers(Context &context) {}
134134
DeinitBarriers(DeinitBarriers const &) = delete;
135135
DeinitBarriers &operator=(DeinitBarriers const &) = delete;
136136
};
137137

138+
class BarrierAccessScopeFinder;
139+
138140
/// Works backwards from the current location of end_borrows to the earliest
139141
/// place they can be hoisted to.
140142
///
141-
/// Implements BackwardReachability::BlockReachability.
143+
/// Implements IterativeBackwardReachability::Effects.
142144
class DataFlow final {
145+
public:
146+
using Reachability = IterativeBackwardReachability<DataFlow>;
147+
using Effect = Reachability::Effect;
148+
using VBAS = VisitBarrierAccessScopes<DataFlow, BarrierAccessScopeFinder>;
149+
150+
private:
143151
Context const &context;
144152
Usage const &uses;
145-
DeinitBarriers &result;
153+
DeinitBarriers &barriers;
154+
Reachability::Result result;
155+
Reachability reachability;
156+
SmallPtrSet<BeginAccessInst *, 8> barrierAccessScopes;
157+
bool recordCopies = false;
146158

147159
enum class Classification { Barrier, Copy, Other };
148160

149-
BackwardReachability<DataFlow> reachability;
150-
151161
public:
152-
DataFlow(Context const &context, Usage const &uses, DeinitBarriers &result)
153-
: context(context), uses(uses), result(result),
154-
reachability(&context.function, *this) {
155-
// Seed reachability with the scope ending uses from which the backwards
156-
// data flow will begin.
157-
for (auto *end : uses.ends) {
158-
reachability.initLastUse(end);
159-
}
160-
}
162+
DataFlow(Context const &context, Usage const &uses, DeinitBarriers &barriers)
163+
: context(context), uses(uses), barriers(barriers),
164+
result(&context.function),
165+
reachability(&context.function, context.defBlock, *this, result) {}
161166
DataFlow(DataFlow const &) = delete;
162167
DataFlow &operator=(DataFlow const &) = delete;
163168

164-
void run() { reachability.solveBackward(); }
169+
void run();
165170

166171
private:
167-
friend class BackwardReachability<DataFlow>;
172+
friend class IterativeBackwardReachability<DataFlow>;
173+
friend class BarrierAccessScopeFinder;
174+
friend VBAS;
168175

169-
bool hasReachableBegin(SILBasicBlock *block) {
170-
return result.hoistingReachesBeginBlocks.contains(block);
171-
}
176+
Classification classifyInstruction(SILInstruction *);
172177

173-
void markReachableBegin(SILBasicBlock *block) {
174-
result.hoistingReachesBeginBlocks.insert(block);
175-
}
178+
bool classificationIsBarrier(Classification);
176179

177-
void markReachableEnd(SILBasicBlock *block) {
178-
result.hoistingReachesEndBlocks.insert(block);
179-
}
180+
ArrayRef<SILInstruction *> gens();
180181

181-
Classification classifyInstruction(SILInstruction *);
182+
Optional<Effect> effectForInstruction(SILInstruction *);
182183

183-
bool classificationIsBarrier(Classification);
184+
Optional<Effect> effectForPhi(SILBasicBlock *);
185+
};
186+
187+
class BarrierAccessScopeFinder final {
188+
using Impl = VisitBarrierAccessScopes<DataFlow, BarrierAccessScopeFinder>;
189+
Context const &context;
190+
Impl impl;
191+
DataFlow &dataflow;
192+
Optional<SmallVector<SILBasicBlock *, 16>> cachedRoots;
184193

185-
void visitedInstruction(SILInstruction *, Classification);
194+
public:
195+
BarrierAccessScopeFinder(Context const &context, DataFlow &dataflow)
196+
: context(context), impl(&context.function, dataflow, *this),
197+
dataflow(dataflow) {}
186198

187-
bool checkReachableBarrier(SILInstruction *);
199+
void find();
188200

189-
bool checkReachablePhiBarrier(SILBasicBlock *);
201+
private:
202+
friend Impl;
203+
204+
ArrayRef<SILBasicBlock *> roots();
205+
bool isInRegion(SILBasicBlock *);
206+
void visitBarrierAccessScope(BeginAccessInst *);
190207
};
191208

192209
/// Whether the specified value is %lifetime or its iterated copy_value.
@@ -207,6 +224,16 @@ bool isSimpleExtendedIntroducerDef(Context const &context, SILValue value) {
207224
}
208225
}
209226

227+
void DataFlow::run() {
228+
reachability.initialize();
229+
BarrierAccessScopeFinder finder(context, *this);
230+
finder.find();
231+
reachability.solve();
232+
recordCopies = true;
233+
reachability.findBarriers(barriers.instructions, barriers.phis,
234+
barriers.blocks);
235+
}
236+
210237
DataFlow::Classification
211238
DataFlow::classifyInstruction(SILInstruction *instruction) {
212239
if (instruction == &context.introducer) {
@@ -220,6 +247,11 @@ DataFlow::classifyInstruction(SILInstruction *instruction) {
220247
if (uses.users.contains(instruction)) {
221248
return Classification::Barrier;
222249
}
250+
if (auto *eai = dyn_cast<EndAccessInst>(instruction)) {
251+
return barrierAccessScopes.contains(eai->getBeginAccess())
252+
? Classification::Barrier
253+
: Classification::Other;
254+
}
223255
if (isDeinitBarrier(instruction)) {
224256
return Classification::Barrier;
225257
}
@@ -237,29 +269,21 @@ bool DataFlow::classificationIsBarrier(Classification classification) {
237269
llvm_unreachable("exhaustive switch not exhaustive?!");
238270
}
239271

240-
void DataFlow::visitedInstruction(SILInstruction *instruction,
241-
Classification classification) {
242-
assert(classifyInstruction(instruction) == classification);
243-
switch (classification) {
244-
case Classification::Barrier:
245-
result.barriers.push_back(instruction);
246-
return;
247-
case Classification::Copy:
248-
result.copies.push_back(cast<CopyValueInst>(instruction));
249-
return;
250-
case Classification::Other:
251-
return;
252-
}
253-
llvm_unreachable("exhaustive switch not exhaustive?!");
254-
}
272+
ArrayRef<SILInstruction *> DataFlow::gens() { return uses.ends; }
255273

256-
bool DataFlow::checkReachableBarrier(SILInstruction *instruction) {
274+
Optional<DataFlow::Effect>
275+
DataFlow::effectForInstruction(SILInstruction *instruction) {
276+
if (llvm::find(uses.ends, instruction) != uses.ends.end())
277+
return {Effect::Gen};
257278
auto classification = classifyInstruction(instruction);
258-
visitedInstruction(instruction, classification);
259-
return classificationIsBarrier(classification);
279+
if (recordCopies && classification == Classification::Copy)
280+
barriers.copies.push_back(cast<CopyValueInst>(instruction));
281+
return classificationIsBarrier(classification)
282+
? Optional<Effect>{Effect::Kill}
283+
: llvm::None;
260284
}
261285

262-
bool DataFlow::checkReachablePhiBarrier(SILBasicBlock *block) {
286+
Optional<DataFlow::Effect> DataFlow::effectForPhi(SILBasicBlock *block) {
263287
assert(llvm::all_of(block->getArguments(),
264288
[&](auto argument) { return PhiValue(argument); }));
265289

@@ -268,10 +292,34 @@ bool DataFlow::checkReachablePhiBarrier(SILBasicBlock *block) {
268292
return classificationIsBarrier(
269293
classifyInstruction(predecessor->getTerminator()));
270294
});
271-
if (isBarrier) {
272-
result.phiBarriers.push_back(block);
295+
return isBarrier ? Optional<Effect>{Effect::Kill} : llvm::None;
296+
}
297+
298+
void BarrierAccessScopeFinder::find() { impl.visit(); }
299+
300+
ArrayRef<SILBasicBlock *> BarrierAccessScopeFinder::roots() {
301+
if (cachedRoots)
302+
return *cachedRoots;
303+
304+
cachedRoots = SmallVector<SILBasicBlock *, 16>{};
305+
BasicBlockSet seenRoots(&context.function);
306+
for (auto *gen : dataflow.gens()) {
307+
seenRoots.insert(gen->getParent());
308+
cachedRoots->push_back(gen->getParent());
309+
}
310+
311+
return *cachedRoots;
312+
}
313+
314+
bool BarrierAccessScopeFinder::isInRegion(SILBasicBlock *block) {
315+
return dataflow.result.discoveredBlocks.contains(block);
316+
}
317+
318+
void BarrierAccessScopeFinder::visitBarrierAccessScope(BeginAccessInst *bai) {
319+
dataflow.barrierAccessScopes.insert(bai);
320+
for (auto *eai : bai->getEndAccesses()) {
321+
dataflow.reachability.addKill(eai);
273322
}
274-
return isBarrier;
275323
}
276324

277325
/// Hoist the scope ends of %lifetime, rewriting copies and borrows along the
@@ -311,7 +359,7 @@ bool Rewriter::run() {
311359
// A block is a phi barrier iff any of its predecessors' terminators get
312360
// classified as barriers. That happens when a copy of %lifetime is passed
313361
// to a phi.
314-
for (auto *block : barriers.phiBarriers) {
362+
for (auto *block : barriers.phis) {
315363
madeChange |= createEndBorrow(&block->front());
316364
}
317365

@@ -324,15 +372,11 @@ bool Rewriter::run() {
324372
// of a block P's successors B had reachable beginnings. If any of them
325373
// didn't, then BackwardReachability::meetOverSuccessors would never have
326374
// returned true for P, so none of its instructions would ever have been
327-
// classified (except for via checkReachablePhiBarrier, which doesn't record
328-
// terminator barriers).
329-
for (auto instruction : barriers.barriers) {
375+
// classified (except for via effectForPhi, which doesn't record terminator
376+
// barriers).
377+
for (auto instruction : barriers.instructions) {
330378
if (auto *terminator = dyn_cast<TermInst>(instruction)) {
331379
auto successors = terminator->getParentBlock()->getSuccessorBlocks();
332-
// In order for the instruction to have been classified as a barrier,
333-
// reachability would have had to reach the block containing it.
334-
assert(barriers.hoistingReachesEndBlocks.contains(
335-
terminator->getParentBlock()));
336380
for (auto *successor : successors) {
337381
madeChange |= createEndBorrow(&successor->front());
338382
}
@@ -356,12 +400,8 @@ bool Rewriter::run() {
356400
// P not having a reachable end--see BackwardReachability::meetOverSuccessors.
357401
//
358402
// control-flow-boundary(B) := beginning-reachable(B) && !end-reachable(P)
359-
for (auto *block : barriers.hoistingReachesBeginBlocks) {
360-
if (auto *predecessor = block->getSinglePredecessorBlock()) {
361-
if (!barriers.hoistingReachesEndBlocks.contains(predecessor)) {
362-
madeChange |= createEndBorrow(&block->front());
363-
}
364-
}
403+
for (auto *block : barriers.blocks) {
404+
madeChange |= createEndBorrow(&block->front());
365405
}
366406

367407
if (madeChange) {
@@ -379,7 +419,7 @@ bool Rewriter::run() {
379419

380420
bool Rewriter::createEndBorrow(SILInstruction *insertionPoint) {
381421
if (auto *ebi = dyn_cast<EndBorrowInst>(insertionPoint)) {
382-
if (uses.ends.contains(insertionPoint)) {
422+
if (llvm::find(uses.ends, insertionPoint) != uses.ends.end()) {
383423
reusedEndBorrowInsts.insert(insertionPoint);
384424
return false;
385425
}

test/SILOptimizer/shrink_borrow_scope.sil

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,12 +458,13 @@ exit:
458458
// =============================================================================
459459

460460
// Don't hoist over loop without uses.
461-
// TODO: Eventually, we should hoist over such loops.
461+
//
462462
// CHECK-LABEL: sil [ossa] @hoist_over_loop_1 : {{.*}} {
463463
// CHECK: {{bb[0-9]+}}([[INSTANCE:%[^,]+]] : @owned $C):
464464
// CHECK: [[LIFETIME:%[^,]+]] = begin_borrow [[INSTANCE]]
465465
// CHECK: [[CALLEE_GUARANTEED:%[^,]+]] = function_ref @callee_guaranteed
466466
// CHECK: {{%[^,]+}} = apply [[CALLEE_GUARANTEED]]([[LIFETIME]])
467+
// CHECK: end_borrow [[LIFETIME]]
467468
// CHECK: br [[LOOP_HEADER:bb[0-9]+]]
468469
// CHECK: [[LOOP_HEADER]]:
469470
// CHECK: br [[LOOP_BODY:bb[0-9]+]]
@@ -474,7 +475,6 @@ exit:
474475
// CHECK: [[LOOP_BACKEDGE]]:
475476
// CHECK: br [[LOOP_HEADER]]
476477
// CHECK: [[EXIT]]:
477-
// CHECK: end_borrow [[LIFETIME]]
478478
// CHECK: return [[INSTANCE]]
479479
// CHECK-LABEL: } // end sil function 'hoist_over_loop_1'
480480
sil [ossa] @hoist_over_loop_1 : $@convention(thin) (@owned C) -> @owned C {

0 commit comments

Comments
 (0)