mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Directly implement the data flow. Eliminate the extraneous work. Remove cubic behavior. Do a single iteration of the data flow state at each program point only performing the necessary set operations. At unidentified access, clear the sets for simplicity and efficiency. This cleanup results in significant functional changes: - Allowing scopes to merge even if they are enclosed. - Handling unidentified access conservatively. Note that most of the added lines of code are comments. Somehow this cleanup incidentally fixes: <rdar://problem/48514339> swift compiler hangs building project (I expected the subsequent loop summary cleanup to fix that problem.)
361 lines
14 KiB
C++
361 lines
14 KiB
C++
//===--- AccessedStorageAnalysis.h - Accessed Storage Analysis --*- C++ -*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file implements an interprocedural analysis pass that summarizes the
|
|
// dynamically enforced formal accesses within a function. These summaries are
|
|
// used by AccessEnforcementOpts to locally fold access scopes and remove
|
|
// dynamic checks based on whole module analysis.
|
|
//
|
|
// This analysis may return conservative results by setting
|
|
// FunctionAccessedStorage.unidentifiedAccess. This does not imply that all
|
|
// accesses within the function have Unidentified AccessedStorage.
|
|
//
|
|
// Note: This interprocedural analysis can be easily augmented to simultaneously
|
|
// compute FunctionSideEffects, without using a separate analysis, by adding
|
|
// FunctionSideEffects as a member of FunctionAccessedStorage. However, passes
|
|
// that use AccessedStorageAnalysis do not currently need SideEffectAnalysis.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
#ifndef SWIFT_SILOPTIMIZER_ANALYSIS_ACCESSED_STORAGE_ANALYSIS_H_
|
|
#define SWIFT_SILOPTIMIZER_ANALYSIS_ACCESSED_STORAGE_ANALYSIS_H_
|
|
|
|
#include "swift/SIL/MemAccessUtils.h"
|
|
#include "swift/SIL/SILFunction.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SILOptimizer/Analysis/SideEffectAnalysis.h"
|
|
|
|
namespace swift {
|
|
|
|
/// Information about a formal access within a function pertaining to a
|
|
/// particular AccessedStorage location.
|
|
class StorageAccessInfo : public AccessedStorage {
|
|
public:
|
|
StorageAccessInfo(AccessedStorage storage, SILAccessKind accessKind,
|
|
bool noNestedConflict)
|
|
: AccessedStorage(storage) {
|
|
Bits.StorageAccessInfo.accessKind = unsigned(accessKind);
|
|
Bits.StorageAccessInfo.noNestedConflict = noNestedConflict;
|
|
Bits.StorageAccessInfo.storageIndex = 0;
|
|
}
|
|
|
|
// Initialize AccessedStorage from the given storage argument and fill in
|
|
// subclass fields from otherStorageInfo.
|
|
StorageAccessInfo(AccessedStorage storage, StorageAccessInfo otherStorageInfo)
|
|
: StorageAccessInfo(storage, otherStorageInfo.getAccessKind(),
|
|
otherStorageInfo.hasNoNestedConflict()) {}
|
|
|
|
template <typename B>
|
|
StorageAccessInfo(AccessedStorage storage, B *beginAccess)
|
|
: StorageAccessInfo(storage, beginAccess->getAccessKind(),
|
|
beginAccess->hasNoNestedConflict()) {
|
|
// Currently limited to dynamic Read/Modify access.
|
|
assert(beginAccess->getEnforcement() == SILAccessEnforcement::Dynamic);
|
|
}
|
|
|
|
/// Get the merged access kind of all accesses on this storage. If any access
|
|
/// is a Modify, the return Modify, otherwise return Read.
|
|
SILAccessKind getAccessKind() const {
|
|
return SILAccessKind(Bits.StorageAccessInfo.accessKind);
|
|
}
|
|
|
|
void setAccessKind(SILAccessKind accessKind) {
|
|
Bits.StorageAccessInfo.accessKind = unsigned(accessKind);
|
|
}
|
|
|
|
/// Get a unique index for this accessed storage within a function.
|
|
unsigned getStorageIndex() const {
|
|
return Bits.StorageAccessInfo.storageIndex;
|
|
}
|
|
|
|
void setStorageIndex(unsigned index) {
|
|
Bits.StorageAccessInfo.storageIndex = index;
|
|
assert(unsigned(Bits.StorageAccessInfo.storageIndex) == index);
|
|
}
|
|
|
|
/// Return true if all accesses of this storage within a function have the
|
|
/// [no_nested_conflict] flag set.
|
|
bool hasNoNestedConflict() const {
|
|
return Bits.StorageAccessInfo.noNestedConflict;
|
|
}
|
|
|
|
void setNoNestedConflict(bool val) {
|
|
Bits.StorageAccessInfo.noNestedConflict = val;
|
|
}
|
|
|
|
bool mergeFrom(const StorageAccessInfo &RHS);
|
|
|
|
void print(raw_ostream &os) const;
|
|
void dump() const;
|
|
};
|
|
} // namespace swift
|
|
|
|
namespace llvm {
|
|
// Use the same DenseMapInfo for StorageAccessInfo as for AccessedStorage. None
|
|
// of the subclass bitfields participate in the Key.
|
|
template <> struct DenseMapInfo<swift::StorageAccessInfo> {
|
|
static swift::StorageAccessInfo getEmptyKey() {
|
|
auto key = DenseMapInfo<swift::AccessedStorage>::getEmptyKey();
|
|
return static_cast<swift::StorageAccessInfo &>(key);
|
|
}
|
|
|
|
static swift::StorageAccessInfo getTombstoneKey() {
|
|
auto key = DenseMapInfo<swift::AccessedStorage>::getTombstoneKey();
|
|
return static_cast<swift::StorageAccessInfo &>(key);
|
|
}
|
|
static unsigned getHashValue(swift::StorageAccessInfo storage) {
|
|
return DenseMapInfo<swift::AccessedStorage>::getHashValue(storage);
|
|
}
|
|
static bool isEqual(swift::StorageAccessInfo LHS,
|
|
swift::StorageAccessInfo RHS) {
|
|
return DenseMapInfo<swift::AccessedStorage>::isEqual(LHS, RHS);
|
|
}
|
|
};
|
|
}
|
|
|
|
namespace swift {
|
|
using AccessedStorageSet = llvm::SmallDenseSet<StorageAccessInfo, 8>;
|
|
|
|
/// Records each unique AccessedStorage in a set of StorageAccessInfo
|
|
/// objects. Hashing and equality only sees the AccesedStorage data. The
|
|
/// additional StorageAccessInfo bits are recorded as results of this analysis.
|
|
///
|
|
/// Any unidentified accesses are summarized as a single unidentifiedAccess
|
|
/// property. This property may also be used to conservatively summarize
|
|
/// results, either because the call graph is unknown or the access sets are too
|
|
/// large. It does not imply that all accesses have Unidentified
|
|
/// AccessedStorage, which is never allowed for class or global access.
|
|
class AccessedStorageResult {
|
|
AccessedStorageSet storageAccessSet;
|
|
Optional<SILAccessKind> unidentifiedAccess;
|
|
|
|
public:
|
|
AccessedStorageResult() {}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Accessing the results.
|
|
|
|
const AccessedStorageSet &getStorageSet() const { return storageAccessSet; }
|
|
|
|
bool isEmpty() const {
|
|
return storageAccessSet.empty() && !unidentifiedAccess;
|
|
}
|
|
|
|
bool hasUnidentifiedAccess() const { return unidentifiedAccess != None; }
|
|
|
|
/// Return true if the analysis has determined all accesses of otherStorage
|
|
/// have the [no_nested_conflict] flag set.
|
|
///
|
|
/// Only call this if there is no unidentifiedAccess in the region and the
|
|
/// given storage is uniquely identified.
|
|
bool hasNoNestedConflict(const AccessedStorage &otherStorage) const;
|
|
|
|
/// Does any of the accesses represented by this AccessedStorageResult
|
|
/// object conflict with the given access kind and storage.
|
|
bool mayConflictWith(SILAccessKind otherAccessKind,
|
|
const AccessedStorage &otherStorage) const;
|
|
|
|
/// Raw access to the result for a given AccessedStorage location.
|
|
StorageAccessInfo
|
|
getStorageAccessInfo(const AccessedStorage &otherStorage) const;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Constructing the results.
|
|
|
|
void clear() {
|
|
storageAccessSet.clear();
|
|
unidentifiedAccess = None;
|
|
}
|
|
|
|
/// Return true if these effects are fully conservative.
|
|
bool hasWorstEffects() { return unidentifiedAccess == SILAccessKind::Modify; }
|
|
|
|
/// Sets the most conservative effects, if we don't know anything about the
|
|
/// function.
|
|
void setWorstEffects() {
|
|
storageAccessSet.clear();
|
|
unidentifiedAccess = SILAccessKind::Modify;
|
|
}
|
|
|
|
void setUnidentifiedAccess(SILAccessKind kind) { unidentifiedAccess = kind; }
|
|
|
|
/// Merge effects directly from \p RHS.
|
|
bool mergeFrom(const AccessedStorageResult &other);
|
|
|
|
/// Merge the effects represented in calleeAccess into this
|
|
/// FunctionAccessedStorage object. calleeAccess must correspond to at least
|
|
/// one callee at the apply site `fullApply`. Merging drops any local effects,
|
|
/// and translates parameter effects into effects on the caller-side
|
|
/// arguments.
|
|
///
|
|
/// The full caller-side effects at a call site can be obtained with
|
|
/// AccessedStorageAnalysis::getCallSiteEffects().
|
|
bool mergeFromApply(const AccessedStorageResult &calleeAccess,
|
|
FullApplySite fullApply);
|
|
|
|
/// Record any access scopes entered by the given single SIL instruction. 'I'
|
|
/// must not be a FullApply; use mergeFromApply instead.
|
|
void analyzeInstruction(SILInstruction *I);
|
|
|
|
void print(raw_ostream &os) const;
|
|
void dump() const;
|
|
|
|
protected:
|
|
std::pair<AccessedStorageSet::iterator, bool>
|
|
insertStorageAccess(StorageAccessInfo storageAccess) {
|
|
storageAccess.setStorageIndex(storageAccessSet.size());
|
|
return storageAccessSet.insert(storageAccess);
|
|
}
|
|
|
|
bool updateUnidentifiedAccess(SILAccessKind accessKind);
|
|
|
|
bool mergeAccesses(const AccessedStorageResult &other,
|
|
std::function<StorageAccessInfo(const StorageAccessInfo &)>
|
|
transformStorage);
|
|
|
|
template <typename B> void visitBeginAccess(B *beginAccess);
|
|
};
|
|
} // namespace swift
|
|
|
|
namespace swift {
|
|
/// The per-function result of AccessedStorageAnalysis.
|
|
class FunctionAccessedStorage {
|
|
AccessedStorageResult accessResult;
|
|
|
|
public:
|
|
FunctionAccessedStorage() {}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Accessing the results.
|
|
|
|
const AccessedStorageResult &getResult() const { return accessResult; }
|
|
|
|
bool hasUnidentifiedAccess() const {
|
|
return accessResult.hasUnidentifiedAccess();
|
|
}
|
|
|
|
/// Return true if the analysis has determined all accesses of otherStorage
|
|
/// have the [no_nested_conflict] flag set.
|
|
///
|
|
/// Only call this if there is no unidentifiedAccess in the function and the
|
|
/// given storage is uniquely identified.
|
|
bool hasNoNestedConflict(const AccessedStorage &otherStorage) const {
|
|
return accessResult.hasNoNestedConflict(otherStorage);
|
|
}
|
|
|
|
/// Does any of the accesses represented by this FunctionAccessedStorage
|
|
/// object conflict with the given access kind and storage.
|
|
bool mayConflictWith(SILAccessKind otherAccessKind,
|
|
const AccessedStorage &otherStorage) const {
|
|
return accessResult.mayConflictWith(otherAccessKind, otherStorage);
|
|
}
|
|
|
|
/// Raw access to the result for a given AccessedStorage location.
|
|
StorageAccessInfo
|
|
getStorageAccessInfo(const AccessedStorage &otherStorage) const {
|
|
return accessResult.getStorageAccessInfo(otherStorage);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Constructing the results.
|
|
|
|
void clear() { accessResult.clear(); }
|
|
|
|
/// Return true if these effects are fully conservative.
|
|
bool hasWorstEffects() { return accessResult.hasWorstEffects(); }
|
|
|
|
/// Sets the most conservative effects, if we don't know anything about the
|
|
/// function.
|
|
void setWorstEffects() { accessResult.setWorstEffects(); }
|
|
|
|
/// Summarize the given function's effects using this FunctionAccessedStorage
|
|
/// object.
|
|
//
|
|
// Return true if the function's' effects have been fully summarized without
|
|
// visiting it's body.
|
|
bool summarizeFunction(SILFunction *F);
|
|
|
|
/// Summarize the callee side effects of a call instruction using this
|
|
/// FunctionAccessedStorage object without analyzing the callee function
|
|
/// bodies or scheduling the callees for bottom-up propagation.
|
|
///
|
|
/// The side effects are represented from the callee's perspective. Parameter
|
|
/// effects are not translated into information on the caller's argument, and
|
|
/// local effects are not dropped.
|
|
///
|
|
/// Return true if this call-site's effects are summarized without visiting
|
|
/// the callee.
|
|
///
|
|
/// TODO: Summarize ArraySemanticsCall accesses.
|
|
bool summarizeCall(FullApplySite fullApply) {
|
|
assert(accessResult.isEmpty() && "expected uninitialized results.");
|
|
return false;
|
|
}
|
|
|
|
/// Merge effects directly from \p RHS.
|
|
bool mergeFrom(const FunctionAccessedStorage &RHS) {
|
|
return accessResult.mergeFrom(RHS.accessResult);
|
|
}
|
|
|
|
/// Merge the effects represented in calleeAccess into this
|
|
/// FunctionAccessedStorage object. calleeAccess must correspond to at least
|
|
/// one callee at the apply site `fullApply`. Merging drops any local effects,
|
|
/// and translates parameter effects into effects on the caller-side
|
|
/// arguments.
|
|
///
|
|
/// The full caller-side effects at a call site can be obtained with
|
|
/// AccessedStorageAnalysis::getCallSiteEffects().
|
|
bool mergeFromApply(const FunctionAccessedStorage &calleeAccess,
|
|
FullApplySite fullApply) {
|
|
return accessResult.mergeFromApply(calleeAccess.accessResult, fullApply);
|
|
}
|
|
|
|
/// Analyze the side-effects of a single SIL instruction \p I.
|
|
/// Visited callees are added to \p BottomUpOrder until \p RecursionDepth
|
|
/// reaches MaxRecursionDepth.
|
|
void analyzeInstruction(SILInstruction *I) {
|
|
accessResult.analyzeInstruction(I);
|
|
}
|
|
|
|
void print(raw_ostream &os) const { accessResult.print(os); }
|
|
void dump() const { accessResult.dump(); }
|
|
};
|
|
|
|
/// Summarizes the dynamic accesses performed within a function and its
|
|
/// callees.
|
|
///
|
|
/// When summarizing multiple accesses, a Read access indicates that all
|
|
/// accesses are read only. A Write access indicates potential reads and writes.
|
|
///
|
|
/// Unidentified accesses are not recorded individually. Rather, the function is
|
|
/// marked as potentially executing unidentified reads or writes. An incomplete
|
|
/// function, without a known callee set, is considered to have unidentified
|
|
/// writes.
|
|
///
|
|
/// Use the GenericFunctionEffectAnalysis API to get the results of the analysis:
|
|
/// - geEffects(SILFunction*)
|
|
/// - getCallSiteEffects(FunctionEffects &callEffects, FullApplySite fullApply)
|
|
class AccessedStorageAnalysis
|
|
: public GenericFunctionEffectAnalysis<FunctionAccessedStorage> {
|
|
public:
|
|
AccessedStorageAnalysis()
|
|
: GenericFunctionEffectAnalysis<FunctionAccessedStorage>(
|
|
SILAnalysisKind::AccessedStorage) {}
|
|
|
|
static bool classof(const SILAnalysis *S) {
|
|
return S->getKind() == SILAnalysisKind::AccessedStorage;
|
|
}
|
|
};
|
|
|
|
} // end namespace swift
|
|
|
|
#endif // SWIFT_SILOPTIMIZER_ANALYSIS_ACCESSED_STORAGE_ANALYSIS_H_
|