Skip to content

Commit d20a6c3

Browse files
committed
[LexicalDestroyHoisting] Adopt VisitBarrierAccessScopes.
Avoids hoisting destroy_values into unrelated access scopes which could introduce exclusivity violations.
1 parent c5e93fc commit d20a6c3

File tree

2 files changed

+132
-0
lines changed

2 files changed

+132
-0
lines changed

lib/SILOptimizer/Utils/LexicalDestroyHoisting.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "swift/SIL/SILInstruction.h"
2121
#include "swift/SIL/SILValue.h"
2222
#include "swift/SILOptimizer/Analysis/Reachability.h"
23+
#include "swift/SILOptimizer/Analysis/VisitBarrierAccessScopes.h"
2324
#include "swift/SILOptimizer/Utils/CanonicalizeBorrowScope.h"
2425
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
2526
#include "swift/SILOptimizer/Utils/InstructionDeleter.h"
@@ -112,11 +113,14 @@ struct DeinitBarriers final {
112113
DeinitBarriers &operator=(DeinitBarriers const &) = delete;
113114
};
114115

116+
class BarrierAccessScopeFinder;
117+
115118
/// Works backwards from the current location of destroy_values to the earliest
116119
/// place they can be hoisted to.
117120
///
118121
/// Implements IterativeBackwardReachability::Effects
119122
/// Implements IterativeBackwardReachability::bindBarriers::Visitor
123+
/// Implements BarrierAccessScopeFinder::Effects
120124
class Dataflow final {
121125
using Reachability = IterativeBackwardReachability<Dataflow>;
122126
using Effect = Reachability::Effect;
@@ -125,6 +129,7 @@ class Dataflow final {
125129
DeinitBarriers &barriers;
126130
Reachability::Result result;
127131
Reachability reachability;
132+
SmallPtrSet<BeginAccessInst *, 8> barrierAccessScopes;
128133

129134
enum class Classification { Barrier, Other };
130135

@@ -140,6 +145,8 @@ class Dataflow final {
140145

141146
private:
142147
friend Reachability;
148+
friend class BarrierAccessScopeFinder;
149+
friend class VisitBarrierAccessScopes<Dataflow, BarrierAccessScopeFinder>;
143150

144151
Classification classifyInstruction(SILInstruction *);
145152

@@ -154,8 +161,37 @@ class Dataflow final {
154161
void visitBarrierBlock(SILBasicBlock *);
155162
};
156163

164+
/// Finds end_access instructions which are barriers to hoisting because the
165+
/// access scopes they contain barriers to hoisting. Hoisting destroy_values
166+
/// into such access scopes could introduce exclusivity violations.
167+
///
168+
/// Implements BarrierAccessScopeFinder::Visitor
169+
class BarrierAccessScopeFinder final {
170+
using Impl = VisitBarrierAccessScopes<Dataflow, BarrierAccessScopeFinder>;
171+
Context const &context;
172+
Impl impl;
173+
Dataflow &dataflow;
174+
Optional<SmallVector<SILBasicBlock *, 16>> cachedRoots;
175+
176+
public:
177+
BarrierAccessScopeFinder(Context const &context, Dataflow &dataflow)
178+
: context(context), impl(&context.function, dataflow, *this),
179+
dataflow(dataflow) {}
180+
181+
void find();
182+
183+
private:
184+
friend Impl;
185+
186+
ArrayRef<SILBasicBlock *> roots();
187+
bool isInRegion(SILBasicBlock *);
188+
void visitBarrierAccessScope(BeginAccessInst *);
189+
};
190+
157191
void Dataflow::run() {
158192
reachability.initialize();
193+
BarrierAccessScopeFinder finder(context, *this);
194+
finder.find();
159195
reachability.solve();
160196
reachability.findBarriers(*this);
161197
}
@@ -168,6 +204,11 @@ Dataflow::classifyInstruction(SILInstruction *instruction) {
168204
if (uses.users.contains(instruction)) {
169205
return Classification::Barrier;
170206
}
207+
if (auto *eai = dyn_cast<EndAccessInst>(instruction)) {
208+
return barrierAccessScopes.contains(eai->getBeginAccess())
209+
? Classification::Barrier
210+
: Classification::Other;
211+
}
171212
if (isDeinitBarrier(instruction)) {
172213
return Classification::Barrier;
173214
}
@@ -218,6 +259,33 @@ void Dataflow::visitBarrierBlock(SILBasicBlock *block) {
218259
barriers.blocks.push_back(block);
219260
}
220261

262+
void BarrierAccessScopeFinder::find() { impl.visit(); }
263+
264+
ArrayRef<SILBasicBlock *> BarrierAccessScopeFinder::roots() {
265+
if (cachedRoots)
266+
return *cachedRoots;
267+
268+
cachedRoots = SmallVector<SILBasicBlock *, 16>{};
269+
BasicBlockSet seenRoots(&context.function);
270+
for (auto *gen : dataflow.gens()) {
271+
seenRoots.insert(gen->getParent());
272+
cachedRoots->push_back(gen->getParent());
273+
}
274+
275+
return *cachedRoots;
276+
}
277+
278+
bool BarrierAccessScopeFinder::isInRegion(SILBasicBlock *block) {
279+
return dataflow.result.discoveredBlocks.contains(block);
280+
}
281+
282+
void BarrierAccessScopeFinder::visitBarrierAccessScope(BeginAccessInst *bai) {
283+
dataflow.barrierAccessScopes.insert(bai);
284+
for (auto *eai : bai->getEndAccesses()) {
285+
dataflow.reachability.addKill(eai);
286+
}
287+
}
288+
221289
/// Hoist the destroy_values of %value.
222290
class Rewriter final {
223291
Context &context;

test/SILOptimizer/lexical_destroy_hoisting.sil

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,3 +460,67 @@ entry(%instance : @owned $C, %input : $S):
460460
// instruction tests }}
461461
// =============================================================================
462462

463+
// =============================================================================
464+
// access scope tests {{
465+
// =============================================================================
466+
467+
// Don't hoist into an access scope that contains a barrier.
468+
//
469+
// CHECK-LABEL: sil [ossa] @nofold_scoped_load_barrier : {{.*}} {
470+
// CHECK: end_access
471+
// CHECK: end_access
472+
// CHECK: destroy_value
473+
// CHECK-LABEL: // end sil function 'nofold_scoped_load_barrier'
474+
sil [ossa] @nofold_scoped_load_barrier : $@convention(thin) (@owned C, @owned C) -> (@owned C) {
475+
entry(%instance : @owned $C, %other : @owned $C):
476+
%addr = alloc_stack $C
477+
%store_scope = begin_access [modify] [static] %addr : $*C
478+
store %other to [init] %store_scope : $*C
479+
end_access %store_scope : $*C
480+
%load_scope = begin_access [read] [static] %addr : $*C
481+
%value = load [copy] %load_scope : $*C
482+
%barrier = function_ref @barrier : $@convention(thin) () -> ()
483+
apply %barrier() : $@convention(thin) () -> ()
484+
end_access %load_scope : $*C
485+
destroy_addr %addr : $*C
486+
dealloc_stack %addr : $*C
487+
destroy_value %instance : $C
488+
return %value : $C
489+
}
490+
491+
// Access scopes that are open at barrier blocks are barriers. Otherwise, we
492+
// would hoist destroy_values into the scopes when the destroy_values are
493+
// hoisted up to the begin of blocks whose predecessor is the barrier block.
494+
//
495+
// CHECK-LABEL: sil [ossa] @nohoist_into_access_scope_barred_by_barrier_block : {{.*}} {
496+
// CHECK: {{bb[0-9]+}}([[INSTANCE:%[^,]+]] : @owned $C, [[INOUT:%[^,]+]] : $*C):
497+
// CHECK: [[SCOPE:%[^,]+]] = begin_access [modify] [static] [[INOUT]] : $*C
498+
// CHECK: cond_br undef, [[LEFT:bb[0-9]+]],
499+
// CHECK: [[LEFT]]:
500+
// CHECK: end_access [[SCOPE]] : $*C
501+
// CHECK-NEXT: destroy_value [[INSTANCE]] : $C
502+
// CHECK-LABEL: } // end sil function 'nohoist_into_access_scope_barred_by_barrier_block'
503+
sil [ossa] @nohoist_into_access_scope_barred_by_barrier_block : $@convention(thin) (@owned C, @inout C) -> () {
504+
entry(%instance : @owned $C, %second : $*C):
505+
%scope = begin_access [modify] [static] %second : $*C
506+
cond_br undef, left, right
507+
508+
left:
509+
end_access %scope : $*C
510+
%ignore = tuple ()
511+
destroy_value %instance : $C
512+
br exit
513+
514+
right:
515+
end_access %scope : $*C
516+
apply undef(%instance) : $@convention(thin) (@owned C) -> ()
517+
br exit
518+
519+
exit:
520+
%retval = tuple ()
521+
return %retval : $()
522+
}
523+
524+
// =============================================================================
525+
// access scope tests }}
526+
// =============================================================================

0 commit comments

Comments
 (0)