Skip to content

Commit 54cff6d

Browse files
authored
Merge pull request #21779 from atrick/fix-merge-access
Reduce compile time for large strongly connected call graphs.
2 parents d8d3a48 + 5b42469 commit 54cff6d

File tree

3 files changed

+48
-22
lines changed

3 files changed

+48
-22
lines changed

include/swift/SILOptimizer/Analysis/AccessedStorageAnalysis.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
// used by AccessEnforcementOpts to locally fold access scopes and remove
1616
// dynamic checks based on whole module analysis.
1717
//
18+
// This analysis may return conservative results by setting
19+
// FunctionAccessedStorage.unidentifiedAccess. This does not imply that all
20+
// accesses within the function have Unidentified AccessedStorage.
21+
//
1822
// Note: This interprocedural analysis can be easily augmented to simultaneously
1923
// compute FunctionSideEffects, without using a separate analysis, by adding
2024
// FunctionSideEffects as a member of FunctionAccessedStorage. However, passes
@@ -125,7 +129,10 @@ namespace swift {
125129
/// additional StorageAccessInfo bits are recorded as results of this analysis.
126130
///
127131
/// Any unidentified accesses are summarized as a single unidentifiedAccess
128-
/// property.
132+
/// property. This property may also be used to conservatively summarize
133+
/// results, either because the call graph is unknown or the access sets are too
134+
/// large. It does not imply that all accesses have Unidentified
135+
/// AccessedStorage, which is never allowed for class or global access.
129136
class FunctionAccessedStorage {
130137
using AccessedStorageSet = llvm::SmallDenseSet<StorageAccessInfo, 8>;
131138

@@ -164,6 +171,9 @@ class FunctionAccessedStorage {
164171
unidentifiedAccess = None;
165172
}
166173

174+
/// Return true if these effects are fully conservative.
175+
bool hasWorstEffects() { return unidentifiedAccess == SILAccessKind::Modify; }
176+
167177
/// Sets the most conservative effects, if we don't know anything about the
168178
/// function.
169179
void setWorstEffects() {

lib/SILOptimizer/Analysis/AccessedStorageAnalysis.cpp

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -151,29 +151,43 @@ bool FunctionAccessedStorage::updateUnidentifiedAccess(
151151
// substitution will be performed if possible. However, there's no guarantee
152152
// that the merged access values will belong to this function.
153153
//
154-
// Note that we may have `this` == `other` for self-recursion. We still need to
155-
// propagate and merge in that case in case arguments are recursively dependent.
154+
// Return true if these results changed, requiring further propagation through
155+
// the call graph.
156156
bool FunctionAccessedStorage::mergeAccesses(
157157
const FunctionAccessedStorage &other,
158158
std::function<StorageAccessInfo(const StorageAccessInfo &)>
159159
transformStorage) {
160160

161-
// Insertion in DenseMap invalidates the iterator in the rare case of
162-
// self-recursion (`this` == `other`) that passes accessed storage though an
163-
// argument. Rather than complicate the code, make a temporary copy of the
164-
// AccessedStorage.
165-
//
166-
// Also note that the storageAccessIndex from otherStorage is relative to its
167-
// original context and should not be copied into this context.
168-
SmallVector<StorageAccessInfo, 8> otherStorageAccesses;
169-
otherStorageAccesses.reserve(other.storageAccessSet.size());
170-
otherStorageAccesses.append(other.storageAccessSet.begin(),
171-
other.storageAccessSet.end());
161+
// The cost of BottomUpIPAnalysis can be quadratic for large recursive call
162+
// graphs. That cost is multiplied by the size of storageAccessSet. Slowdowns
163+
// can occur ~1000 elements. 200 is large enough to cover "normal" code,
164+
// while ensuring compile time isn't affected.
165+
if (storageAccessSet.size() > 200) {
166+
llvm::dbgs() << "BIG SET " << storageAccessSet.size() << "\n";
167+
setWorstEffects();
168+
return true;
169+
}
170+
// To save compile time, if this storage already has worst-case effects, avoid
171+
// growing its storageAccessSet.
172+
if (hasWorstEffects())
173+
return false;
172174

175+
// When `this` == `other` (for self-recursion), insertion in DenseMap
176+
// invalidates the iterator. We still need to propagate and merge in that case
177+
// because arguments can be recursively dependent. The alternative would be
178+
// treating all self-recursion conservatively.
179+
const FunctionAccessedStorage *otherFunctionAccesses = &other;
180+
FunctionAccessedStorage functionAccessCopy;
181+
if (this == &other) {
182+
functionAccessCopy = other;
183+
otherFunctionAccesses = &functionAccessCopy;
184+
}
173185
bool changed = false;
174-
for (auto &rawStorageInfo : otherStorageAccesses) {
186+
// Nondeterminstically iterate for the sole purpose of inserting into another
187+
// unordered set.
188+
for (auto &rawStorageInfo : otherFunctionAccesses->storageAccessSet) {
175189
const StorageAccessInfo &otherStorageInfo =
176-
transformStorage(rawStorageInfo);
190+
transformStorage(rawStorageInfo);
177191
// If transformStorage() returns invalid storage object for local storage,
178192
// that should not be merged with the caller.
179193
if (!otherStorageInfo)

lib/SILOptimizer/Transforms/AccessEnforcementWMO.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
///
1616
/// This maps each access of identified storage onto a disjoint access
1717
/// location. Local accesses (Box and Stack) already have unique AccessedStorage
18-
/// and should be removed by an earlier function pass. This pass handles Class
19-
/// and Global access by partitioning their non-unique AccessedStorage objects
20-
/// into unique DisjointAccessLocations. These disjoint access locations may be
21-
/// accessed across multiple functions, so a module pass is required to identify
22-
/// and optimize them.
18+
/// and should be removed by an earlier function pass if possible. This pass
19+
/// handles Class and Global access by partitioning their non-unique
20+
/// AccessedStorage objects into unique DisjointAccessLocations. These disjoint
21+
/// access locations may be accessed across multiple functions, so a module pass
22+
/// is required to identify and optimize them.
2323
///
2424
/// Class accesses are partitioned by their fully qualified property
2525
/// name. Global accesses are partitioned by the global variable name. Argument
@@ -48,7 +48,9 @@
4848
///
4949
/// Note: This optimization must be aware of all possible access to a Class or
5050
/// Global address. This includes unpaired access instructions and keypath
51-
/// instructions. Ignoring any access pattern would weaken enforcement.
51+
/// instructions. Ignoring any access pattern would weaken enforcement. For
52+
/// example, AccessedStorageAnalysis cannot be used here because that analysis
53+
/// may conservatively summarize some functions.
5254
//===----------------------------------------------------------------------===//
5355

5456
#define DEBUG_TYPE "access-enforcement-wmo"

0 commit comments

Comments
 (0)