Files
swift-mirror/lib/SILOptimizer/Transforms/AccessEnforcementOpts.cpp
Andrew Trick 4f13dedc93 Add and enable an AccessEnforcementWMO pass.
Remove dynamic access checks for global variables and and class properties that
have been proven by earlier analysis never to conflict with nested access.

This only applies "-O -enforce-exclusivity=checked" builds. By default, it is
currently NFC.

These are the most important improvements:

RecursiveOwnedParameter                           -93.7%   **15.75x**
ReversedArray                                     -89.1%   **9.17x**
ReversedDictionary                                -81.0%   **5.28x**
ArrayInClass                                      -74.3%   **3.89x**
Ary3                                              -71.7%   **3.54x**
Random                                            -66.2%   **2.96x**
StringWalk                                        -63.5%   **2.74x**
LevenshteinDistance                               -55.4%   **2.24x**
Voronoi                                           -50.0%   **2.00x**
HashTest                                          -47.7%   **1.91x**
Richards                                          -46.7%   **1.88x**
NopDeinit                                         -44.8%   **1.81x**
Rectangles                                        -41.3%   **1.70x**
MultiFileTogether                                 -33.1%   **1.50x**
MultiFileSeparate                                 -32.8%   **1.49x**
SetIntersect_OfObjects                            -26.5%   **1.36x**
Ary2                                              -22.7%   **1.29x**
Prims                                             -21.9%   **1.28x**
PrimsSplit                                        -21.8%   **1.28x**
SetExclusiveOr_OfObjects                          -19.4%   **1.24x**
ObjectAllocation                                  -18.6%   **1.23x**
DropFirstAnySeqCRangeIterLazy                     -17.2%   **1.21x**
DropFirstAnySeqCRangeIter                         -17.2%   **1.21x**
Dictionary4OfObjects                              -16.5%   **1.20x**
SetUnion_OfObjects                                -15.3%   **1.18x**
DropWhileCountableRangeLazy                       -15.3%   **1.18x**
CharIndexing_[*]_Backwards                        -14.6%   **1.17x**
(all 17 variants of CharIndexing are -14%, 1.17x)
CharIteration_[*]_Backwards                       -14.3%   **1.17x**
(all 17 variants of CharIteration take 14%, 1.17x)
RGBHistogramOfObjects                             -14.2%   **1.17x**
DeltaBlue                                         -13.5%   **1.16x**
CharacterPropertiesPrecomputed                    -12.4%   **1.14x**
DictionarySwapOfObjects                           -9.9%    **1.11x**
ClassArrayGetter                                  -9.8%    **1.11x**
DictionaryGroupOfObjects                          -7.9%    **1.09x**
DictionaryRemoveOfObjects                         -7.2%    **1.08x**
Dictionary4OfObjectsLegacy                        -6.8%    **1.07x**
Havlak                                            -6.4%    **1.07x**
COWTree                                           -6.2%    **1.07x**
Radix2CooleyTukeyf                                -5.6%    **1.06x**
2018-06-29 17:56:56 -07:00

660 lines
26 KiB
C++

//===------ AccessEnforcementOpts.cpp - Optimize access enforcement -------===//
//
// 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
//
//===----------------------------------------------------------------------===//
///
/// Pass order dependencies:
///
/// - Will benefit from running after AccessEnforcementSelection.
///
/// - Should run immediately before the AccessEnforcementWMO to share
/// AccessedStorageAnalysis results.
///
/// This pass optimizes access enforcement as follows:
///
/// **Access marker folding**
///
/// Find begin/end access scopes that are uninterrupted by a potential
/// conflicting access. Flag those as [nontracking] access.
///
/// Folding must prove that no dynamic conflicts occur inside of an access
/// scope. That is, a scope has no "nested inner conflicts". The access itself
/// may still conflict with an outer scope. If successful, folding simply sets
/// the [no_nested_conflict] attribute on the begin_[unpaired_]access
/// instruction and removes all corresponding end_[unpaired_]access
/// instructions.
///
/// This analysis is conceptually similar to DiagnoseStaticExclusivity. The
/// difference is that it conservatively considers any dynamic access that may
/// alias, as opposed to only the obviously aliasing accesses (it is the
/// complement of the static diagnostic pass in that respect). This makes a
/// considerable difference in the implementation. For example,
/// DiagnoseStaticExclusivity must be able to fully analyze all @inout_aliasable
/// parameters because they aren't dynamically enforced. This optimization
/// completely ignores @inout_aliasable paramters because it only cares about
/// dynamic enforcement. This optimization also does not attempt to
/// differentiate accesses on disjoint subaccess paths, because it should not
/// weaken enforcement in any way--a program that traps at -Onone should also
/// trap at -O.
///
/// Access folding is a forward data flow analysis that tracks open accesses. If
/// any path to an access' end of scope has a potentially conflicting access,
/// then that access is marked as a nested conflict.
///
/// **Local access marker removal**
///
/// When none of the local accesses on local storage (box/stack) have nested
/// conflicts, then all the local accesses may be disabled by setting their
/// enforcement to `static`. This is somwhat rare because static diagnostics
/// already promote the obvious cases to static checks. However, there are two
/// reasons that dynamic local markers may be disabled: (1) inlining may cause
/// closure access to become local access (2) local storage may truly escape,
/// but none of the the local access scopes cross a call site.
///
/// TODO: Perform another run of AccessEnforcementSelection immediately before
/// this pass. Currently, that pass only works well when run before
/// AllocBox2Stack. Ideally all such closure analysis passes are combined into a
/// shared analysis with a set of associated optimizations that can be rerun at
/// any point in the pipeline. Until then, we could settle for a partially
/// working AccessEnforcementSelection, or expand it somewhat to handle
/// alloc_stack.
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "access-enforcement-opts"
#include "swift/SIL/DebugUtils.h"
#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SILOptimizer/Analysis/AccessedStorageAnalysis.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/Local.h"
#include "llvm/ADT/SmallBitVector.h"
using namespace swift;
namespace swift {
/// Represents the identity of a storage location being accessed.
///
/// A value-based subclass of AccessedStorage with identical layout. This
/// provides access to pass-specific data in reserved bits.
///
/// The fully descriptive class name allows forward declaration in order to
/// define bitfields in AccessedStorage.
///
/// Aliased to AccessInfo in this file.
class AccessEnforcementOptsInfo : public AccessedStorage {
public:
AccessEnforcementOptsInfo(const AccessedStorage &storage)
: AccessedStorage(storage) {
Bits.AccessEnforcementOptsInfo.beginAccessIndex = 0;
Bits.AccessEnforcementOptsInfo.seenNestedConflict = false;
}
/// Get a unique index for this access within its function.
unsigned getAccessIndex() const {
return Bits.AccessEnforcementOptsInfo.beginAccessIndex;
}
void setAccessIndex(unsigned index) {
Bits.AccessEnforcementOptsInfo.beginAccessIndex = index;
assert(unsigned(Bits.AccessEnforcementOptsInfo.beginAccessIndex) == index);
}
/// Has the analysis seen a conflicting nested access on any path within this
/// access' scope.
bool seenNestedConflict() const {
return Bits.AccessEnforcementOptsInfo.seenNestedConflict;
}
void setSeenNestedConflict() {
Bits.AccessEnforcementOptsInfo.seenNestedConflict = 1;
}
void dump() const {
AccessedStorage::dump();
llvm::dbgs() << " access index: " << getAccessIndex() << " <"
<< (seenNestedConflict() ? "" : "no ") << "conflict>\n";
}
};
using AccessInfo = AccessEnforcementOptsInfo;
} // namespace swift
namespace {
// Sparse access sets are used temporarily for fast operations by local
// reachability analysis. We don't care if they take more space.
class SparseAccessSet;
/// A dense set of begin_access instructions as a compact vector. Reachability
/// results are stored here because very few accesses are typically in-progress
/// at a particular program point, particularly at block boundaries.
using DenseAccessSet = SmallVector<BeginAccessInst *, 4>;
/// Analyze a function's formal access to determine nested conflicts.
///
/// Maps each begin access instruction to its AccessInfo, which:
/// - identifies the accessed memory for conflict detection
/// - contains a pass-specific reachability set index
/// - contains a pass-specific flag that indicates the presence of a conflict
/// on any path.
///
/// If, after computing reachability, an access' conflict flag is still not set,
/// then all paths in its scope are conflict free. Reachability begins at a
/// begin_access instruction and ends either at a potential conflict
/// or at the end_access instruction that is associated with the
/// begin_access.
///
/// Forward data flow computes `blockOutAccess` for each block. At a control
/// flow merge, this analysis forms an intersection of reachable accesses on
/// each path. Only visited predecessors are merged (unvisited paths
/// optimistically assume reachability). Before a block is visited, it has no
/// map entry in blockOutAccess. Blocks are processed in RPO order, and a single
/// begin_access dominates all associated end_access instructions. Consequently,
/// when a block is first visited, blockOutAccess contains the maximal
/// reachability set. Further iteration would only reduce this set.
///
/// The only result of this analysis is the seenNestedConflict flags in
/// AccessInfo. Since reducing a reachability set cannot further detect
/// conflicts, there is no need to iterate to a reachability fix point.
class AccessConflictAnalysis {
public:
using AccessMap = llvm::SmallDenseMap<BeginAccessInst *, AccessInfo, 32>;
// This result of this analysis is a map from all BeginAccessInst in this
// function to AccessInfo.
struct Result {
/// Map each begin access to its AccessInfo with index, data, and flags.
/// Iterating over this map is nondeterministic. If it is necessary to order
/// the accesses, then AccessInfo::getAccessIndex() can be used.
AccessMap accessMap;
/// Convenience.
///
/// Note: If AccessInfo has already been retrieved, get the index directly
/// from it instead of calling this to avoid additional hash lookup.
unsigned getAccessIndex(BeginAccessInst *beginAccess) const {
return getAccessInfo(beginAccess).getAccessIndex();
}
/// Get the AccessInfo for a BeginAccessInst within this function. All
/// accesses are mapped by identifyBeginAccesses().
AccessInfo &getAccessInfo(BeginAccessInst *beginAccess) {
auto iter = accessMap.find(beginAccess);
assert(iter != accessMap.end());
return iter->second;
}
const AccessInfo &getAccessInfo(BeginAccessInst *beginAccess) const {
return const_cast<Result &>(*this).getAccessInfo(beginAccess);
}
};
private:
SILFunction *F;
PostOrderFunctionInfo *PO;
AccessedStorageAnalysis *ASA;
/// Tracks the in-scope accesses at the end of each block, for the purpose of
/// finding nested conflicts. (Out-of-scope accesses are currently only
/// tracked locally for the purpose of merging access scopes.)
llvm::SmallDenseMap<SILBasicBlock *, DenseAccessSet, 32> blockOutAccess;
Result result;
public:
AccessConflictAnalysis(SILFunction *F, PostOrderFunctionInfo *PO,
AccessedStorageAnalysis *ASA)
: F(F), PO(PO), ASA(ASA) {}
Result &&analyze() &&;
protected:
void identifyBeginAccesses();
void visitBeginAccess(BeginAccessInst *beginAccess,
SparseAccessSet &accessMap);
void visitEndAccess(EndAccessInst *endAccess, SparseAccessSet &accessMap);
void visitFullApply(FullApplySite fullApply, SparseAccessSet &accessMap);
void mergePredAccesses(SILBasicBlock *succBB,
SparseAccessSet &mergedAccesses);
void visitBlock(SILBasicBlock *BB);
};
/// A sparse set of in-flight accesses.
///
/// Explodes a DenseAccessSet into a temporary sparse set for comparison
/// and membership.
///
/// The AccessConflictAnalysis result provides the instruction to index
/// mapping.
class SparseAccessSet {
AccessConflictAnalysis::Result &result;
// Mark the in-scope accesses.
// (Most functions have < 64 accesses.)
llvm::SmallBitVector inScopeBitmask;
// Mark a potential conflicts on each access since the last begin/end marker.
llvm::SmallBitVector conflictBitmask;
DenseAccessSet denseVec; // Hold all local accesses seen thus far.
public:
/// Iterate over in-scope, conflict free access.
class NoNestedConflictIterator {
const SparseAccessSet &sparseSet;
DenseAccessSet::const_iterator denseIter;
public:
NoNestedConflictIterator(const SparseAccessSet &set)
: sparseSet(set), denseIter(set.denseVec.begin()) {}
BeginAccessInst *next() {
auto end = sparseSet.denseVec.end();
while (denseIter != end) {
BeginAccessInst *beginAccess = *denseIter;
++denseIter;
unsigned sparseIndex = sparseSet.result.getAccessIndex(beginAccess);
if (sparseSet.inScopeBitmask[sparseIndex]
&& !sparseSet.conflictBitmask[sparseIndex]) {
return beginAccess;
}
}
return nullptr;
}
};
SparseAccessSet(AccessConflictAnalysis::Result &result)
: result(result), inScopeBitmask(result.accessMap.size()),
conflictBitmask(result.accessMap.size()) {}
// All accessed in the given denseVec are presumed to be in-scope and conflict
// free.
SparseAccessSet(const DenseAccessSet &denseVec,
AccessConflictAnalysis::Result &result)
: result(result), inScopeBitmask(result.accessMap.size()),
conflictBitmask(result.accessMap.size()), denseVec(denseVec) {
for (BeginAccessInst *beginAccess : denseVec)
inScopeBitmask.set(result.getAccessIndex(beginAccess));
}
bool hasConflictFreeAccess() const {
NoNestedConflictIterator iterator(*this);
return iterator.next() != nullptr;
}
bool hasInScopeAccess() const {
return llvm::any_of(denseVec, [this](BeginAccessInst *beginAccess) {
unsigned sparseIndex = result.getAccessIndex(beginAccess);
return inScopeBitmask[sparseIndex];
});
}
bool isInScope(unsigned index) const { return inScopeBitmask[index]; }
// Insert the given BeginAccessInst with its corresponding reachability index.
// Set the in-scope bit and reset the conflict bit.
bool enterScope(BeginAccessInst *beginAccess, unsigned index) {
assert(!inScopeBitmask[index]
&& "nested access should not be dynamically enforced.");
inScopeBitmask.set(index);
conflictBitmask.reset(index);
denseVec.push_back(beginAccess);
return true;
}
/// End the scope of the given access by marking it in-scope and clearing the
/// conflict bit. (The conflict bit only marks conflicts since the last begin
/// *or* end access).
void exitScope(unsigned index) { inScopeBitmask.reset(index); }
bool seenConflict(unsigned index) const { return conflictBitmask[index]; }
void setConflict(unsigned index) { conflictBitmask.set(index); }
// Only merge accesses that are present on the `other` map. i.e. erase
// all accesses in this map that are not present in `other`.
void merge(const SparseAccessSet &other) {
inScopeBitmask &= other.inScopeBitmask;
// Currently only conflict free accesses are preserved across blocks by this
// analysis. Otherwise, taking the union of conflict bits would be valid.
assert(other.conflictBitmask.none());
}
void copyNoNestedConflictInto(DenseAccessSet &other) {
other.clear();
NoNestedConflictIterator iterator(*this);
while (BeginAccessInst *beginAccess = iterator.next())
other.push_back(beginAccess);
}
// Dump only the accesses with no conflict up to this point.
void dump() const {
for (BeginAccessInst *beginAccess : denseVec) {
unsigned sparseIndex = result.getAccessIndex(beginAccess);
if (conflictBitmask[sparseIndex])
continue;
llvm::dbgs() << *beginAccess << " ";
if (!inScopeBitmask[sparseIndex])
llvm::dbgs() << " [noscope]";
result.getAccessInfo(beginAccess).dump();
}
}
};
} // namespace
// Top-level driver for AccessConflictAnalysis.
AccessConflictAnalysis::Result &&AccessConflictAnalysis::analyze() && {
// Populate beginAccessMap.
identifyBeginAccesses();
// Perform data flow and set the conflict flags on AccessInfo.
for (auto *BB : PO->getReversePostOrder())
visitBlock(BB);
return std::move(result);
}
// Find all begin access operations in this function. Map each access to
// AccessInfo, which includes its identified memory location, identifying
// index, and analysis result flags.
//
// TODO: begin_unpaired_access is not tracked. Even though begin_unpaired_access
// isn't explicitly paired, it may be possible after devirtualization and
// inlining to find all uses of the scratch buffer. However, this doesn't
// currently happen in practice (rdar://40033735).
void AccessConflictAnalysis::identifyBeginAccesses() {
for (auto &BB : *F) {
for (auto &I : BB) {
auto *beginAccess = dyn_cast<BeginAccessInst>(&I);
if (!beginAccess)
continue;
if (beginAccess->getEnforcement() != SILAccessEnforcement::Dynamic)
continue;
// The accessed base is expected to be valid for begin_access, but for
// now, since this optimization runs at the end of the pipeline, we
// gracefully ignore unrecognized source address patterns, which show up
// here as an invalid `storage` value.
const AccessedStorage &storage =
findAccessedStorageNonNested(beginAccess->getSource());
if (!storage)
continue;
auto iterAndSuccess = result.accessMap.try_emplace(
beginAccess, static_cast<const AccessInfo &>(storage));
(void)iterAndSuccess;
assert(iterAndSuccess.second);
// Add a pass-specific access index to the mapped storage object.
AccessInfo &info = iterAndSuccess.first->second;
info.setAccessIndex(result.accessMap.size() - 1);
assert(!info.seenNestedConflict());
}
}
}
/// When a conflict is detected, flag the access so it can't be folded, and
/// remove its index from the current access set so we stop checking for
/// conflicts. Erasing from SparseAccessSet does not invalidate any iterators.
static void recordConflict(AccessInfo &info, SparseAccessSet &accessSet) {
info.setSeenNestedConflict();
accessSet.setConflict(info.getAccessIndex());
}
// Given an "inner" access, check for potential conflicts with any outer access.
// Allow these overlapping accesses:
// - read/read
// - different bases, both valid, and at least one is local
//
// Remove any outer access that may conflict from the accessSet.
// and flag the conflict in beginAccessMap.
//
// Record the inner access in the accessSet.
//
void AccessConflictAnalysis::visitBeginAccess(BeginAccessInst *innerBeginAccess,
SparseAccessSet &accessSet) {
if (innerBeginAccess->getEnforcement() != SILAccessEnforcement::Dynamic)
return;
const AccessInfo &innerAccess = result.getAccessInfo(innerBeginAccess);
SILAccessKind innerAccessKind = innerBeginAccess->getAccessKind();
SparseAccessSet::NoNestedConflictIterator accessIter(accessSet);
while (BeginAccessInst *outerBeginAccess = accessIter.next()) {
// If both are reads, keep the mapped access.
if (!accessKindMayConflict(innerAccessKind,
outerBeginAccess->getAccessKind())) {
continue;
}
AccessInfo &outerAccess = result.getAccessInfo(outerBeginAccess);
// If there is no potential conflict, leave the outer access mapped.
if (!outerAccess.isDistinctFrom(innerAccess))
continue;
DEBUG(innerAccess.dump(); llvm::dbgs() << " may conflict with:\n";
outerAccess.dump());
recordConflict(outerAccess, accessSet);
}
DEBUG(llvm::dbgs() << "Recording access: " << *innerBeginAccess;
llvm::dbgs() << " at: "; innerAccess.dump());
// Record the current access in the map. It can potentially be folded
// regardless of whether it may conflict with an outer access.
bool inserted =
accessSet.enterScope(innerBeginAccess, innerAccess.getAccessIndex());
(void)inserted;
assert(inserted && "the same begin_access cannot be seen twice.");
}
void AccessConflictAnalysis::visitEndAccess(EndAccessInst *endAccess,
SparseAccessSet &accessSet) {
auto *beginAccess = endAccess->getBeginAccess();
if (beginAccess->getEnforcement() != SILAccessEnforcement::Dynamic)
return;
unsigned index = result.getAccessIndex(beginAccess);
DEBUG(if (accessSet.seenConflict(index)) llvm::dbgs()
<< "No conflict on one path from " << *beginAccess << " to "
<< *endAccess);
// Erase this access from the sparse set. We only want to detect conflicts
// within the access scope.
accessSet.exitScope(index);
}
void AccessConflictAnalysis::visitFullApply(FullApplySite fullApply,
SparseAccessSet &accessSet) {
FunctionAccessedStorage callSiteAccesses;
ASA->getCallSiteEffects(callSiteAccesses, fullApply);
SparseAccessSet::NoNestedConflictIterator accessIter(accessSet);
while (BeginAccessInst *outerBeginAccess = accessIter.next()) {
// If there is no potential conflict, leave the outer access mapped.
SILAccessKind accessKind = outerBeginAccess->getAccessKind();
AccessInfo &outerAccess = result.getAccessInfo(outerBeginAccess);
if (!callSiteAccesses.mayConflictWith(accessKind, outerAccess))
continue;
DEBUG(llvm::dbgs() << *fullApply.getInstruction() << " call site access: ";
callSiteAccesses.dump(); llvm::dbgs() << " may conflict with:\n";
outerAccess.dump());
recordConflict(outerAccess, accessSet);
}
}
// Merge all predecessor accesses into the local acces set. Only propagate
// accesses that are still present in all predecessors. The absence of a begin
// access from a visited predecessor indicates the presence of a conflict. A
// block has been visited if it has a map entry in blockOutAccess.
void AccessConflictAnalysis::mergePredAccesses(SILBasicBlock *succBB,
SparseAccessSet &mergedAccesses) {
for (SILBasicBlock *predBB : succBB->getPredecessorBlocks()) {
auto mapI = blockOutAccess.find(predBB);
if (mapI == blockOutAccess.end())
continue;
const DenseAccessSet &predSet = mapI->second;
mergedAccesses.merge(SparseAccessSet(predSet, result));
}
}
// Compute access reachability within the given block.
void AccessConflictAnalysis::visitBlock(SILBasicBlock *BB) {
// Sparse set for tracking accesses with an individual block.
SparseAccessSet accessSet(result);
mergePredAccesses(BB, accessSet);
for (auto &I : *BB) {
if (auto *BAI = dyn_cast<BeginAccessInst>(&I)) {
visitBeginAccess(BAI, accessSet);
continue;
}
if (auto *EAI = dyn_cast<EndAccessInst>(&I)) {
visitEndAccess(EAI, accessSet);
continue;
}
if (auto fullApply = FullApplySite::isa(&I)) {
visitFullApply(fullApply, accessSet);
}
}
DEBUG(if (accessSet.hasConflictFreeAccess()) {
llvm::dbgs() << "Initializing no-conflict access out of bb"
<< BB->getDebugID() << "\n";
accessSet.dump();
});
if (BB->getTerminator()->isFunctionExiting())
assert(!accessSet.hasInScopeAccess() && "no postdominating end_access");
// Initialize blockOutAccess for this block with the current access set.
accessSet.copyNoNestedConflictInto(blockOutAccess[BB]);
}
// -----------------------------------------------------------------------------
// MARK: Access Enforcement Optimization
// -----------------------------------------------------------------------------
/// Perform access folding.
///
/// Data-flow analysis is now complete. Any begin_access that has seen a
/// conflict can be given the [no_nested_conflict] instruction attribute.
///
/// Note: If we later support marking begin_unpaired_access
/// [no_nested_conflict], then we also need to remove any corresponding
/// end_unpaired_access. That can be done either by recording the
/// end_unpaired_access instructions during analysis and deleting them here in
/// the same order, or sorting them here by their begin_unpaired_access index.
static bool
foldNonNestedAccesses(AccessConflictAnalysis::AccessMap &accessMap) {
bool changed = false;
// Iteration over accessMap is nondeterministic. Setting the conflict flags
// can be done in any order.
for (auto &beginAccessAndInfo : accessMap) {
BeginAccessInst *beginAccess = beginAccessAndInfo.first;
AccessInfo &info = beginAccessAndInfo.second;
if (info.seenNestedConflict())
continue;
// Optimize this begin_access by setting [no_nested_conflict].
beginAccess->setNoNestedConflict(true);
changed = true;
DEBUG(llvm::dbgs() << "Folding " << *beginAccess);
}
return changed;
}
/// Perform local access marker elimination.
///
/// Disable accesses to uniquely identified local storage for which no
/// accesses can have nested conflicts. This is only valid if the function's
/// local storage cannot be potentially modified by unidentified access:
///
/// - Arguments cannot alias with local storage, so accessing an argument has no
/// effect on analysis of the current function. When a callee accesses an
/// argument, AccessedStorageAnalysis will either map the accessed storage to
/// a value in the caller's function, or mark it as unidentified.
///
/// - Stack or Box local storage could potentially be accessed via Unidentified
/// access. (Some Unidentified accesses are for initialization or for
/// temporary storage instead, but those should never have Dynamic
/// enforcement). These accesses can only be eliminated when there is no
/// Unidentified access within the function without the [no_nested_conflict]
/// flag.
static bool
removeLocalNonNestedAccess(const AccessConflictAnalysis::Result &result,
const FunctionAccessedStorage &functionAccess) {
if (functionAccess.hasUnidentifiedAccess())
return false;
bool changed = false;
SmallVector<BeginAccessInst *, 8> deadAccesses;
for (auto &beginAccessAndInfo : result.accessMap) {
BeginAccessInst *beginAccess = beginAccessAndInfo.first;
const AccessInfo &info = beginAccessAndInfo.second;
if (info.seenNestedConflict() || !info.isLocal())
continue;
// This particular access to local storage is marked
// [no_nested_conflict]. Now check FunctionAccessedStorage to determine if
// that is true for all access to the same storage.
if (functionAccess.hasNoNestedConflict(info)) {
DEBUG(llvm::dbgs() << "Disabling dead access " << *beginAccess);
beginAccess->setEnforcement(SILAccessEnforcement::Static);
changed = true;
}
}
return changed;
}
namespace {
struct AccessEnforcementOpts : public SILFunctionTransform {
void run() override {
SILFunction *F = getFunction();
if (F->empty())
return;
DEBUG(llvm::dbgs() << "Running local AccessEnforcementOpts on "
<< F->getName() << "\n");
PostOrderFunctionInfo *PO = getAnalysis<PostOrderAnalysis>()->get(F);
AccessedStorageAnalysis *ASA = getAnalysis<AccessedStorageAnalysis>();
auto result = AccessConflictAnalysis(F, PO, ASA).analyze();
// Perform access folding by setting the [no_nested_conflict] flag on
// begin_access instructions.
if (!foldNonNestedAccesses(result.accessMap))
return;
// Recompute AccessStorageAnalysis, just for this function, to update the
// StorageAccessInfo::noNestedConflict status for each accessed storage.
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
// Use the updated AccessedStorageAnalysis to find any uniquely identified
// local storage that has no nested conflict on any of its accesses within
// this function. All the accesses can be marked as statically enforced.
//
// Note that the storage address may be passed as an argument and there may
// be nested conflicts within that call, but none of the accesses within
// this function will overlap.
const FunctionAccessedStorage &functionAccess = ASA->getEffects(F);
if (removeLocalNonNestedAccess(result, functionAccess))
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}
};
} // namespace
SILTransform *swift::createAccessEnforcementOpts() {
return new AccessEnforcementOpts();
}