Skip to content

Commit 271bfdc

Browse files
committed
[SSADestroyHoisting] Note barrier access scopes.
Added a second backward reachability data flow that determines whether any open access scopes contain barriers. The end_access instructions for those access scopes are themselves barriers.
1 parent 767e509 commit 271bfdc

File tree

1 file changed

+149
-9
lines changed

1 file changed

+149
-9
lines changed

lib/SILOptimizer/Transforms/SSADestroyHoisting.cpp

Lines changed: 149 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,14 @@ class DeinitBarriers {
204204
// Debug instructions that are no longer within this lifetime after shrinking.
205205
SmallVector<SILInstruction *, 4> deadUsers;
206206

207+
// The access scopes which are hoisting barriers.
208+
//
209+
// They are hoisting barriers if they include any barriers. We need to be
210+
// sure not to hoist a destroy_addr into an access scope and by doing so cause
211+
// a deinit which had previously executed outside an access scope to start
212+
// executing within it--that could violate exclusivity.
213+
llvm::SmallPtrSet<BeginAccessInst *, 8> barrierAccessScopes;
214+
207215
explicit DeinitBarriers(bool ignoreDeinitBarriers,
208216
const KnownStorageUses &knownUses,
209217
SILFunction *function)
@@ -215,11 +223,15 @@ class DeinitBarriers {
215223
storageDefInst = rootValue->getDefiningInstruction();
216224
}
217225

218-
void compute() { DestroyReachability(*this).solveBackward(); }
226+
void compute() {
227+
FindBarrierAccessScopes(*this).solveBackward();
228+
DestroyReachability(*this).solveBackward();
229+
}
219230

220231
bool isBarrier(SILInstruction *instruction) const {
221-
return classificationIsBarrier(classifyInstruction(
222-
instruction, ignoreDeinitBarriers, storageDefInst, knownUses));
232+
return classificationIsBarrier(
233+
classifyInstruction(instruction, ignoreDeinitBarriers, storageDefInst,
234+
barrierAccessScopes, knownUses));
223235
};
224236

225237
private:
@@ -234,20 +246,136 @@ class DeinitBarriers {
234246

235247
Classification classifyInstruction(SILInstruction *inst) {
236248
return classifyInstruction(inst, ignoreDeinitBarriers, storageDefInst,
237-
knownUses);
249+
barrierAccessScopes, knownUses);
238250
}
239251

240-
static Classification classifyInstruction(SILInstruction *inst,
241-
bool ignoreDeinitBarriers,
242-
SILInstruction *storageDefInst,
243-
const KnownStorageUses &knownUses);
252+
static Classification classifyInstruction(
253+
SILInstruction *inst, bool ignoreDeinitBarriers,
254+
SILInstruction *storageDefInst,
255+
const llvm::SmallPtrSetImpl<BeginAccessInst *> &barrierAccessScopes,
256+
const KnownStorageUses &knownUses);
244257

245258
void visitedInstruction(SILInstruction *instruction,
246259
Classification classification);
247260

248261
static bool classificationIsBarrier(Classification classification);
249262

250263
// Implements BackwardReachability::BlockReachability
264+
//
265+
// Determine which end_access instructions must be treated as barriers.
266+
//
267+
// An end_access is a barrier if the access scope it ends contains any deinit
268+
// barriers. Suppose that it weren't treated as a barrier. Then the
269+
// destroy_addr would be hoisted up to the in-scope deinit barrier. That
270+
// could result in a deinit being executed within the scope which was
271+
// previously executed outside it. Executing a deinit in the scope could
272+
// violate exclusivity.
273+
//
274+
// So before determining what ALL the barriers are, we need to determine which
275+
// end_access instructions are barriers. Do that by observing which access
276+
// scopes are open when encountering a barrier. The access scopes which are
277+
// open are those for which we've seen an end_access instruction when walking
278+
// backwards from the destroy_addrs. Add these access scopes to
279+
// DeinitBarriers::barrierAccessScopes.
280+
//
281+
// Tracking which access scopes are open consists of two parts:
282+
// (1) in-block analysis
283+
// (2) cross-block analysis
284+
// For (1), maintain a set of access scopes which are currently open. Insert
285+
// and erase scopes when seeing begin_access and end_access instructions when
286+
// they're visited in checkReachableBarrier. A stack can't be used here
287+
// because access scopes are not necessarily nested.
288+
// For (2), when entering a block, the access scope is the union of all the
289+
// open access scopes in the block's predecessors.
290+
class FindBarrierAccessScopes {
291+
DeinitBarriers &result;
292+
BasicBlockSetVector destroyReachesBeginBlocks;
293+
llvm::DenseMap<SILBasicBlock *, llvm::SmallPtrSet<BeginAccessInst *, 2>>
294+
liveInAccessScopes;
295+
llvm::SmallPtrSet<BeginAccessInst *, 2> runningLiveAccessScopes;
296+
297+
BackwardReachability<FindBarrierAccessScopes> reachability;
298+
299+
public:
300+
FindBarrierAccessScopes(DeinitBarriers &result)
301+
: result(result),
302+
destroyReachesBeginBlocks(result.knownUses.getFunction()),
303+
reachability(result.knownUses.getFunction(), *this) {
304+
// Seed backward reachability with destroy points.
305+
for (SILInstruction *destroy : result.knownUses.originalDestroys) {
306+
reachability.initLastUse(destroy);
307+
}
308+
}
309+
310+
void markLiveAccessScopesAsBarriers() {
311+
for (auto *scope : runningLiveAccessScopes) {
312+
result.barrierAccessScopes.insert(scope);
313+
}
314+
}
315+
316+
bool hasReachableBegin(SILBasicBlock *block) {
317+
return destroyReachesBeginBlocks.contains(block);
318+
}
319+
320+
void markReachableBegin(SILBasicBlock *block) {
321+
destroyReachesBeginBlocks.insert(block);
322+
if (!runningLiveAccessScopes.empty()) {
323+
liveInAccessScopes[block] = runningLiveAccessScopes;
324+
}
325+
}
326+
327+
void markReachableEnd(SILBasicBlock *block) {
328+
runningLiveAccessScopes.clear();
329+
for (auto *predecessor : block->getPredecessorBlocks()) {
330+
auto iterator = liveInAccessScopes.find(predecessor);
331+
if (iterator != liveInAccessScopes.end()) {
332+
for (auto *bai : iterator->getSecond()) {
333+
runningLiveAccessScopes.insert(bai);
334+
}
335+
}
336+
}
337+
}
338+
339+
bool checkReachableBarrier(SILInstruction *inst) {
340+
// For correctness, it is required that
341+
// FindBarrierAccessScopes::checkReachableBarrier return true whenever
342+
// DestroyReachability::checkReachableBarrier does, with one exception:
343+
// DestryReachability::checkReachableBarrier will also return true for any
344+
// end_access barrier that FindBarrierAccessScopes finds.
345+
if (auto *eai = dyn_cast<EndAccessInst>(inst)) {
346+
runningLiveAccessScopes.insert(eai->getBeginAccess());
347+
} else if (auto *bai = dyn_cast<BeginAccessInst>(inst)) {
348+
runningLiveAccessScopes.erase(bai);
349+
}
350+
bool isBarrier = result.isBarrier(inst);
351+
if (isBarrier) {
352+
markLiveAccessScopesAsBarriers();
353+
}
354+
// If we've seen a barrier, then we can stop looking for access scopes.
355+
// Any that were open already have now been marked as barriers. And if
356+
// none are open, the second data flow won't get beyond this barrier to
357+
// face subsequent end_access instructions.
358+
return isBarrier;
359+
}
360+
361+
bool checkReachablePhiBarrier(SILBasicBlock *block) {
362+
bool isBarrier =
363+
llvm::any_of(block->getPredecessorBlocks(), [&](auto *predecessor) {
364+
return result.isBarrier(block->getTerminator());
365+
});
366+
if (isBarrier) {
367+
// If there's a barrier preventing us from hoisting out of this block,
368+
// then every open access scope contains a barrier, so all the
369+
// corresponding end_access instructions are barriers too.
370+
markLiveAccessScopesAsBarriers();
371+
}
372+
return isBarrier;
373+
}
374+
375+
void solveBackward() { reachability.solveBackward(); }
376+
};
377+
378+
// Conforms to BackwardReachability::BlockReachability
251379
class DestroyReachability {
252380
DeinitBarriers &result;
253381

@@ -284,7 +412,9 @@ class DeinitBarriers {
284412

285413
DeinitBarriers::Classification DeinitBarriers::classifyInstruction(
286414
SILInstruction *inst, bool ignoreDeinitBarriers,
287-
SILInstruction *storageDefInst, const KnownStorageUses &knownUses) {
415+
SILInstruction *storageDefInst,
416+
const llvm::SmallPtrSetImpl<BeginAccessInst *> &barrierAccessScopes,
417+
const KnownStorageUses &knownUses) {
288418
if (knownUses.debugInsts.contains(inst)) {
289419
return Classification::DeadUser;
290420
}
@@ -297,6 +427,11 @@ DeinitBarriers::Classification DeinitBarriers::classifyInstruction(
297427
if (!ignoreDeinitBarriers && isDeinitBarrier(inst)) {
298428
return Classification::Barrier;
299429
}
430+
if (auto *eai = dyn_cast<EndAccessInst>(inst)) {
431+
return barrierAccessScopes.contains(eai->getBeginAccess())
432+
? Classification::Barrier
433+
: Classification::Other;
434+
}
300435
return Classification::Other;
301436
}
302437

@@ -334,6 +469,11 @@ void DeinitBarriers::visitedInstruction(SILInstruction *instruction,
334469
/// which is a storageUser and therefore a barrier.
335470
bool DeinitBarriers::DestroyReachability::checkReachableBarrier(
336471
SILInstruction *instruction) {
472+
// For correctness, it is required that
473+
// DestroyReachability::checkReachableBarrier return true whenever
474+
// FindBarrierAccessScopes::checkReachableBarrier does. It must additionally
475+
// return true when encountering an end_access barrier that
476+
// FindBarrierAccessScope determined is a barrier.
337477
auto classification = result.classifyInstruction(instruction);
338478
result.visitedInstruction(instruction, classification);
339479
return result.classificationIsBarrier(classification);

0 commit comments

Comments
 (0)