blob: 553f51d9aeacd357745577f111c04ae1faae87ad [file] [log] [blame]
//===--- 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