Skip to content

Commit f4c7d46

Browse files
committed
Change the algorithm for the AccessEnforcementDom pass.
This adds a mostly flow-insensitive analysis that runs before the dominator-based transformations. The analysis is simple and efficient because it only needs to track data flow of currently in-scope accesses. The original dominator tree walk remains simple, but it now checks the flow insensitive analysis information to determine general correctness. This is now correct in the presence of all kinds of nested static and dynamic nested accesses, call sites, coroutines, etc. This is a better compromise than: (a) disabling the pass and taking a major performance loss. (b) converting the pass itself to full-fledged data flow driven optimization, which would be more optimal because it could remove accesses when nesting is involved, but would be much more expensive and complicated, and there's no indication that it's useful. The new approach is also simpler than adding more complexity to independently handle to each of many issues: - Nested reads followed by a modify without a false conflict. - Reads nested within a function call without a false conflict. - Conflicts nested within a function call without dropping enforcement. - Accesses within a generalized accessor. - Conservative treatment of invalid storage locations. - Conservative treatment of unknown apply callee. - General analysis invalidation. Some of these issues also needed to be considered in the LoopDominatingAccess sub-pass. Rather than fix that sub-pass, I just integrated it into the main pass. This is a simplification, is more efficient, and also handles nested loops without creating more redundant accesses. It is also generalized to: - hoist non-uniquely identified accesses. - Avoid unnecessarily promoting accesses inside the loop. With this approach we can remove the scary warnings and caveats in the comments. While doing this I also took the opportunity to eliminate quadratic behavior, make the domtree walk non-recursive, and eliminate cutoff thresholds. Note that simple nested dynamic reads to identical storage could very easily be removed via separate logic, but it does not fit with the dominator-based algorithm. For example, during the analysis phase, we could simply mark the "fully nested" read scopes, then convert them to [static] right after the analysis, removing them from the result map. I didn't do this because I don't know if it happens in practice.
1 parent d6eb168 commit f4c7d46

File tree

10 files changed

+1384
-405
lines changed

10 files changed

+1384
-405
lines changed

include/swift/SIL/MemAccessUtils.h

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ class AccessedStorage {
160160
64 - NumAccessedStorageBits,
161161
seenNestedConflict : 1,
162162
beginAccessIndex : 63 - NumAccessedStorageBits);
163+
164+
// Define data flow bits for use in the AccessEnforcementDom pass. Each
165+
// begin_access in the function is mapped to one instance of this subclass.
166+
SWIFT_INLINE_BITFIELD(DomAccessedStorage, AccessedStorage, 1 + 1,
167+
isInner : 1, containsRead : 1);
163168
} Bits;
164169

165170
private:
@@ -190,6 +195,10 @@ class AccessedStorage {
190195

191196
Kind getKind() const { return static_cast<Kind>(Bits.AccessedStorage.Kind); }
192197

198+
// Clear any bits reserved for subclass data. Useful for up-casting back to
199+
// the base class.
200+
void resetSubclassData() { initKind(getKind()); }
201+
193202
SILValue getValue() const {
194203
assert(getKind() != Argument && getKind() != Global && getKind() != Class);
195204
return value;
@@ -215,6 +224,10 @@ class AccessedStorage {
215224
return objProj;
216225
}
217226

227+
/// Return true if the given storage objects have identical storage locations.
228+
///
229+
/// This compares only the AccessedStorage base class bits, ignoring the
230+
/// subclass bits. It is used for hash lookup equality.
218231
bool hasIdenticalBase(const AccessedStorage &other) const {
219232
if (getKind() != other.getKind())
220233
return false;
@@ -233,7 +246,6 @@ class AccessedStorage {
233246
case Class:
234247
return objProj == other.objProj;
235248
}
236-
llvm_unreachable("unhandled kind");
237249
}
238250

239251
/// Return true if the storage is guaranteed local.
@@ -311,31 +323,6 @@ class AccessedStorage {
311323
bool operator!=(const AccessedStorage &) const = delete;
312324
};
313325

314-
/// Return true if the given storage objects have identical storage locations.
315-
///
316-
/// This compares only the AccessedStorage base class bits, ignoring the
317-
/// subclass bits.
318-
inline bool accessingIdenticalLocations(AccessedStorage LHS,
319-
AccessedStorage RHS) {
320-
if (LHS.getKind() != RHS.getKind())
321-
return false;
322-
323-
switch (LHS.getKind()) {
324-
case swift::AccessedStorage::Box:
325-
case swift::AccessedStorage::Stack:
326-
case swift::AccessedStorage::Nested:
327-
case swift::AccessedStorage::Yield:
328-
case swift::AccessedStorage::Unidentified:
329-
return LHS.getValue() == RHS.getValue();
330-
case swift::AccessedStorage::Argument:
331-
return LHS.getParamIndex() == RHS.getParamIndex();
332-
case swift::AccessedStorage::Global:
333-
return LHS.getGlobal() == RHS.getGlobal();
334-
case swift::AccessedStorage::Class:
335-
return LHS.getObjectProjection() == RHS.getObjectProjection();
336-
}
337-
}
338-
339326
template <class ImplTy, class ResultTy = void, typename... ArgTys>
340327
class AccessedStorageVisitor {
341328
ImplTy &asImpl() { return static_cast<ImplTy &>(*this); }
@@ -396,7 +383,7 @@ template <> struct DenseMapInfo<swift::AccessedStorage> {
396383
}
397384

398385
static bool isEqual(swift::AccessedStorage LHS, swift::AccessedStorage RHS) {
399-
return swift::accessingIdenticalLocations(LHS, RHS);
386+
return LHS.hasIdenticalBase(RHS);
400387
}
401388
};
402389

lib/SIL/MemAccessUtils.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ AccessedStorage swift::findAccessedStorage(SILValue sourceAddr) {
425425
}
426426
// `storage` may still be invalid. If both `storage` and `result` are
427427
// invalid, this check passes, but we return an invalid storage below.
428-
if (!accessingIdenticalLocations(storage.getValue(), result.getStorage()))
428+
if (!storage.getValue().hasIdenticalBase(result.getStorage()))
429429
return AccessedStorage();
430430
}
431431
return storage.getValueOr(AccessedStorage());

0 commit comments

Comments
 (0)