| //===--- 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 |