Skip to content

Commit 1ba3066

Browse files
committed
CanonicalOSSALifetime: Add support for overlapping access scopes.
Access scopes for enforcing exclusivity are currently the only exception to our ability to canonicalize OSSA lifetime purely based on the SSA value's known uses. This is because access scopes have semantics relative to object deinitializers. In general, deinitializers are asynchronous with respect to code that is unrelated to the object's uses. Ignoring exclusivity, the optimizer may always destroy objects as early as it wants, as long as the object won't be used again. The optimizer may also extend the lifetime (although in the future this lifetime extension should be limited by "synchronization points"). The optimizer's freedom is however limited by exclusivity enforcement. Optimization may never introduce new exclusivity violations. Destroying an object within an access scope is an exclusivity violation if the deinitializer accesses the same variable. To handle this, OSSA canonicalization must detect access scopes that overlap with the end of the pruned extended lifetime. Essentially: %def begin_access // access scope unrelated to def use %def // pruned liveness ends here end_access destroy %def Support for access scopes composes cleanly with the existing algorithm without adding significant cost in the usual case. Overlapping access scopes are unusual. A single CFG walk within the original extended lifetime is normally sufficient. Only the blocks that are not already LiveOut in the pruned liveness need to be visited. During this walk, local overlapping access are detected by scanning for end_access instructions after the last use point. Global overlapping accesses are detected by checking NonLocalAccessBlockAnalysis. This avoids scanning instructions in the common case. NonLocalAccessBlockAnalysis is a trivial analysis that caches the rare occurence of nonlocal access scopes. The analysis itself is a single linear scan over the instruction stream. This analysis can be preserved across most transformations and I expect it to be used to speed up other optimizations related to access marker. When an overlapping access is detected, pruned liveness is simply extended to include the end_access as a new use point. Extending the lifetime is iterative, but with each iteration, blocks that are now marked LiveOut no longer need to be visited. Furthermore, interleaved accessed scopes are not expected to happen in practice.
1 parent bd85d83 commit 1ba3066

File tree

12 files changed

+854
-25
lines changed

12 files changed

+854
-25
lines changed

include/swift/Basic/Compiler.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,11 @@
121121
// }
122122
// };
123123
#ifdef NDEBUG
124-
#define SWIFT_ASSERT_ONLY_DECL(X)
125-
#define SWIFT_ASSERT_ONLY(X) do { } while (false)
124+
#define SWIFT_ASSERT_ONLY_DECL(...)
125+
#define SWIFT_ASSERT_ONLY(...) do { } while (false)
126126
#else
127-
#define SWIFT_ASSERT_ONLY_DECL(X) X
128-
#define SWIFT_ASSERT_ONLY(X) do { X; } while (false)
127+
#define SWIFT_ASSERT_ONLY_DECL(X...) X
128+
#define SWIFT_ASSERT_ONLY(X...) do { X; } while (false)
129129
#endif
130130

131131
#endif // SWIFT_BASIC_COMPILER_H

include/swift/SILOptimizer/Analysis/Analysis.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ ANALYSIS(Escape)
3838
ANALYSIS(InductionVariable)
3939
ANALYSIS(Loop)
4040
ANALYSIS(LoopRegion)
41+
ANALYSIS(NonLocalAccessBlock)
4142
ANALYSIS(OptimizerStats)
4243
ANALYSIS(PostDominance)
4344
ANALYSIS(PostOrder)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//===--- NonLocalAccessBlockAnalysis.h - Nonlocal end_access ----*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
///
13+
/// Cache the set of blocks that contain a non-local end_access, which is a rare
14+
/// occurrence. Optimizations that are driven by a known-use analysis, such as
15+
/// CanonicalOSSA, don't need to scan instructions that are unrelated to the SSA
16+
/// def-use graph. However, they may still need to be aware of unrelated access
17+
/// scope boundaries. By querying this analysis, they can avoid scanning all
18+
/// instructions just to deal with the extremely rare case of an end_access that
19+
/// spans blocks within the relevant SSA lifetime.
20+
///
21+
/// By default, this analysis is invalidated whenever instructions or blocks are
22+
/// changed, but it should ideally be preserved by passes that invalidate
23+
/// instructions but don't create any new access scopes or move end_access
24+
/// across blocks, which is unusual.
25+
///
26+
//===----------------------------------------------------------------------===//
27+
28+
#ifndef SWIFT_SILOPTIMIZER_ANALYSIS_NONLOCALACCESSBLOCKS_H
29+
#define SWIFT_SILOPTIMIZER_ANALYSIS_NONLOCALACCESSBLOCKS_H
30+
31+
#include "swift/Basic/Compiler.h"
32+
#include "swift/SILOptimizer/Analysis/Analysis.h"
33+
#include "llvm/ADT/SmallPtrSet.h"
34+
35+
namespace swift {
36+
37+
class SILBasicBlock;
38+
class SILFunction;
39+
40+
class NonLocalAccessBlocks {
41+
friend class NonLocalAccessBlockAnalysis;
42+
43+
SILFunction *function;
44+
llvm::SmallPtrSet<SILBasicBlock *, 4> accessBlocks;
45+
46+
public:
47+
NonLocalAccessBlocks(SILFunction *function) : function(function) {}
48+
49+
SILFunction *getFunction() const { return function; }
50+
51+
bool containsNonLocalEndAccess(SILBasicBlock *block) const {
52+
return accessBlocks.count(block);
53+
}
54+
55+
/// Perform NonLocalAccessBlockAnalysis for this function. Populate
56+
/// this->accessBlocks with all blocks containing a non-local end_access.
57+
void compute();
58+
};
59+
60+
class NonLocalAccessBlockAnalysis
61+
: public FunctionAnalysisBase<NonLocalAccessBlocks> {
62+
public:
63+
static bool classof(const SILAnalysis *S) {
64+
return S->getKind() == SILAnalysisKind::NonLocalAccessBlock;
65+
}
66+
NonLocalAccessBlockAnalysis()
67+
: FunctionAnalysisBase<NonLocalAccessBlocks>(
68+
SILAnalysisKind::NonLocalAccessBlock) {}
69+
70+
NonLocalAccessBlockAnalysis(const NonLocalAccessBlockAnalysis &) = delete;
71+
72+
NonLocalAccessBlockAnalysis &
73+
operator=(const NonLocalAccessBlockAnalysis &) = delete;
74+
75+
protected:
76+
virtual std::unique_ptr<NonLocalAccessBlocks>
77+
newFunctionAnalysis(SILFunction *function) override {
78+
auto result = std::make_unique<NonLocalAccessBlocks>(function);
79+
result->compute();
80+
return result;
81+
}
82+
83+
virtual bool shouldInvalidate(SILAnalysis::InvalidationKind kind) override {
84+
return kind & InvalidationKind::BranchesAndInstructions;
85+
}
86+
87+
SWIFT_ASSERT_ONLY_DECL(
88+
virtual void verify(NonLocalAccessBlocks *accessBlocks) const override {
89+
NonLocalAccessBlocks checkAccessBlocks(accessBlocks->function);
90+
checkAccessBlocks.compute();
91+
assert(checkAccessBlocks.accessBlocks == accessBlocks->accessBlocks);
92+
})
93+
};
94+
95+
} // end namespace swift
96+
97+
#endif

include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,12 @@
9393
#ifndef SWIFT_SILOPTIMIZER_UTILS_CANONICALOSSALIFETIME_H
9494
#define SWIFT_SILOPTIMIZER_UTILS_CANONICALOSSALIFETIME_H
9595

96-
#include "llvm/ADT/DenseMap.h"
97-
#include "llvm/ADT/SetVector.h"
9896
#include "swift/SIL/SILInstruction.h"
97+
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
98+
#include "swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h"
9999
#include "swift/SILOptimizer/Utils/PrunedLiveness.h"
100+
#include "llvm/ADT/DenseMap.h"
101+
#include "llvm/ADT/SetVector.h"
100102

101103
namespace swift {
102104

@@ -187,6 +189,13 @@ class CanonicalizeOSSALifetime {
187189
/// liveness may be pruned during canonicalization.
188190
bool pruneDebug;
189191

192+
NonLocalAccessBlockAnalysis *accessBlockAnalysis;
193+
// Lazilly initialize accessBlocks only when
194+
// extendLivenessThroughOverlappingAccess is invoked.
195+
NonLocalAccessBlocks *accessBlocks = nullptr;
196+
197+
DominanceAnalysis *dominanceAnalysis;
198+
190199
/// Current copied def for which this state describes the liveness.
191200
SILValue currentDef;
192201

@@ -208,7 +217,7 @@ class CanonicalizeOSSALifetime {
208217
/// Record all interesting debug_value instructions here rather then treating
209218
/// them like a normal use. An interesting debug_value is one that may lie
210219
/// outisde the pruned liveness at the time it is discovered.
211-
llvm::SmallDenseSet<DebugValueInst *, 8> debugValues;
220+
llvm::SmallPtrSet<DebugValueInst *, 8> debugValues;
212221

213222
/// Reuse a general worklist for def-use traversal.
214223
SmallSetVector<SILValue, 8> defUseWorklist;
@@ -226,12 +235,19 @@ class CanonicalizeOSSALifetime {
226235
CanonicalOSSAConsumeInfo consumes;
227236

228237
public:
229-
CanonicalizeOSSALifetime(bool pruneDebug) : pruneDebug(pruneDebug) {}
238+
CanonicalizeOSSALifetime(bool pruneDebug,
239+
NonLocalAccessBlockAnalysis *accessBlockAnalysis,
240+
DominanceAnalysis *dominanceAnalysis)
241+
: pruneDebug(pruneDebug), accessBlockAnalysis(accessBlockAnalysis),
242+
dominanceAnalysis(dominanceAnalysis) {}
230243

231244
SILValue getCurrentDef() const { return currentDef; }
232245

233246
void initDef(SILValue def) {
234247
assert(consumingBlocks.empty() && debugValues.empty() && liveness.empty());
248+
// Clear the cached analysis pointer just in case the client invalidates the
249+
// analysis, freeing its memory.
250+
accessBlocks = nullptr;
235251
consumes.clear();
236252

237253
currentDef = def;
@@ -281,6 +297,10 @@ class CanonicalizeOSSALifetime {
281297

282298
bool computeCanonicalLiveness();
283299

300+
bool endsAccessOverlappingPrunedBoundary(SILInstruction *inst);
301+
302+
void extendLivenessThroughOverlappingAccess();
303+
284304
void findOrInsertDestroyInBlock(SILBasicBlock *bb);
285305

286306
void findOrInsertDestroys();

include/swift/SILOptimizer/Utils/PrunedLiveness.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ class PrunedLiveBlocks {
147147
}
148148

149149
/// Update this liveness result for a single use.
150-
IsLive updateForUse(Operand *use);
150+
IsLive updateForUse(SILInstruction *user);
151151

152152
IsLive getBlockLiveness(SILBasicBlock *bb) const {
153153
auto liveBlockIter = liveBlocks.find(bb);
@@ -217,7 +217,7 @@ class PrunedLiveness {
217217
/// relationships that generate liveness. For example, use->isLifetimeEnding()
218218
/// cannot distinguish the end of the borrow scope that defines this extended
219219
/// live range vs. a nested borrow scope within the extended live range.
220-
void updateForUse(Operand *use, bool lifetimeEnding);
220+
void updateForUse(SILInstruction *user, bool lifetimeEnding);
221221

222222
PrunedLiveBlocks::IsLive getBlockLiveness(SILBasicBlock *bb) const {
223223
return liveBlocks.getBlockLiveness(bb);

lib/SILOptimizer/Analysis/Analysis.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "swift/SILOptimizer/Analysis/ClassHierarchyAnalysis.h"
2121
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
2222
#include "swift/SILOptimizer/Analysis/IVAnalysis.h"
23+
#include "swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h"
2324
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
2425
#include "swift/SILOptimizer/Analysis/ProtocolConformanceAnalysis.h"
2526
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
@@ -60,3 +61,7 @@ SILAnalysis *swift::createBasicCalleeAnalysis(SILModule *M) {
6061
SILAnalysis *swift::createProtocolConformanceAnalysis(SILModule *M) {
6162
return new ProtocolConformanceAnalysis(M);
6263
}
64+
65+
SILAnalysis *swift::createNonLocalAccessBlockAnalysis(SILModule *M) {
66+
return new NonLocalAccessBlockAnalysis();
67+
}

lib/SILOptimizer/Analysis/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ target_sources(swiftSILOptimizer PRIVATE
1919
LoopAnalysis.cpp
2020
LoopRegionAnalysis.cpp
2121
MemoryBehavior.cpp
22+
NonLocalAccessBlockAnalysis.cpp
2223
PassManagerVerifierAnalysis.cpp
2324
ProtocolConformanceAnalysis.cpp
2425
RCIdentityAnalysis.cpp
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//===--- NonLocalAccessBlockAnalysis.cpp - Nonlocal end_access -----------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h"
14+
#include "swift/SIL/SILFunction.h"
15+
16+
using namespace swift;
17+
18+
// Populate this->accessBlocks with
19+
void NonLocalAccessBlocks::compute() {
20+
for (SILBasicBlock &block : *this->function) {
21+
for (SILInstruction &inst : block) {
22+
if (auto *endAccess = dyn_cast<EndAccessInst>(&inst)) {
23+
if (endAccess->getBeginAccess()->getParent() != endAccess->getParent())
24+
this->accessBlocks.insert(&block);
25+
} else if (isa<EndUnpairedAccessInst>(inst)) {
26+
this->accessBlocks.insert(&block);
27+
}
28+
}
29+
}
30+
}

lib/SILOptimizer/Transforms/CopyPropagation.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ class CopyPropagation : public SILFunctionTransform {
5252
/// Top-level pass driver.
5353
void CopyPropagation::run() {
5454
auto *f = getFunction();
55+
auto *accessBlockAnalysis = getAnalysis<NonLocalAccessBlockAnalysis>();
56+
auto *dominanceAnalysis = getAnalysis<DominanceAnalysis>();
5557

5658
// Debug label for unit testing.
5759
LLVM_DEBUG(llvm::dbgs() << "*** CopyPropagation: " << f->getName() << "\n");
@@ -70,7 +72,8 @@ void CopyPropagation::run() {
7072
}
7173
}
7274
// Perform copy propgation for each copied value.
73-
CanonicalizeOSSALifetime canonicalizer(pruneDebug);
75+
CanonicalizeOSSALifetime canonicalizer(pruneDebug, accessBlockAnalysis,
76+
dominanceAnalysis);
7477
for (auto &def : copiedDefs) {
7578
canonicalizer.canonicalizeValueLifetime(def);
7679
if (SILValue outerCopy = canonicalizer.createdOuterCopy()) {
@@ -81,7 +84,10 @@ void CopyPropagation::run() {
8184
// live ranges.
8285
}
8386
if (canonicalizer.hasChanged()) {
87+
// Preserves NonLocalAccessBlockAnalysis.
88+
accessBlockAnalysis->lockInvalidation();
8489
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
90+
accessBlockAnalysis->unlockInvalidation();
8591
DeadEndBlocks deBlocks(f);
8692
f->verifyOwnership(&deBlocks);
8793
}

0 commit comments

Comments
 (0)