mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
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**
660 lines
26 KiB
C++
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();
|
|
}
|