blob: 6c8c761b079d389a094a1683281b20b556f4ccb6 [file] [log] [blame]
//===------ 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.
///
/// **Access marker merger**
///
/// When a pair of non-overlapping accesses, where the first access dominates
/// the second and there are no conflicts on the same storage in the paths
/// between them, and they are part of the same sub-region
/// be it the same block or the sampe loop, merge those accesses to create
/// a new, larger, scope with a single begin_access for the accesses.
//===----------------------------------------------------------------------===//
#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/Analysis/DominanceAnalysis.h"
#include "swift/SILOptimizer/Analysis/LoopRegionAnalysis.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/Local.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/SCCIterator.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 {
/// A dense map of (index, 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 = llvm::SmallSetVector<BeginAccessInst *, 4>;
// Tracks the local data flow result for a basic block
struct RegionInfo {
struct AccessSummary {
// The actual begin_access instructions
DenseAccessSet conflictFreeAccesses;
// Flag to Indicate if we started a merging process
bool merged;
AccessSummary(unsigned size) : merged(false) {}
};
AccessSummary inScopeConflictFreeAccesses;
AccessSummary outOfScopeConflictFreeAccesses;
bool unidentifiedAccess;
public:
RegionInfo(unsigned size)
: inScopeConflictFreeAccesses(size), outOfScopeConflictFreeAccesses(size),
unidentifiedAccess(false) {}
void reset() {
inScopeConflictFreeAccesses.conflictFreeAccesses.clear();
outOfScopeConflictFreeAccesses.conflictFreeAccesses.clear();
outOfScopeConflictFreeAccesses.merged = true;
unidentifiedAccess = false;
}
const DenseAccessSet &getInScopeAccesses() {
return inScopeConflictFreeAccesses.conflictFreeAccesses;
}
const DenseAccessSet &getOutOfScopeAccesses() {
return outOfScopeConflictFreeAccesses.conflictFreeAccesses;
}
};
/// Analyze a function's formal accesses.
/// determines nested conflicts and mergeable accesses.
///
/// 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 `BlockRegionInfo` for each region's blocks.
/// Loops are processed bottom-up.
/// Control flow within a loop or function top level is processed in RPO order.
/// At a block's control flow merge, this analysis forms an intersection of
/// reachable accesses on each path inside the region.
/// Before a block is visited, it has no `BlockRegionInfo` entry.
/// Blocks are processed in RPO order, and a single begin_access dominates
/// all associated end_access instructions. Consequently,
/// when a block is first visited, its storage accesses contains the maximal
/// reachability set. Further iteration would only reduce this set.
///
/// The only results of this analysis are:
//// 1) The seenNestedConflict flags in AccessInfo. For Each begin_access
/// Since reducing a reachability set cannot further detect
/// conflicts, there is no need to iterate to a reachability fix point.
/// This is derived from a block's in-scope accesses
/// 2) A deterministic order map of out-of-scope instructions that we can
/// merge. The way we construct this map guarantees the accesses within
/// it are mergeable.
///
// Example:
// %1 = begin_access X
// %1 is in-scope
// ...
// %2 = begin_access Y // conflict with %1 if X (may-)aliases Y
// If it conflicts - seenNestedConflict
// ...
// end_access %1
// %1 is out-of-scope
// ...
// %3 = begin_access X // %1 reaches %3 -> we can merge
class AccessConflictAndMergeAnalysis {
public:
using AccessMap = llvm::SmallDenseMap<BeginAccessInst *, AccessInfo, 32>;
using AccessedStorageSet = llvm::SmallDenseSet<AccessedStorage, 8>;
using LoopRegionToAccessedStorage =
llvm::SmallDenseMap<unsigned, AccessedStorageSet>;
using RegionIDToLocalInfoMap = llvm::DenseMap<unsigned, RegionInfo>;
// Instruction pairs we can merge from dominating instruction to dominated
using MergeablePairs =
llvm::SmallVector<std::pair<BeginAccessInst *, BeginAccessInst *>, 64>;
// 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.
/// This maps contains every dynamic begin_access instruction,
/// even those with invalid storage:
/// We would like to keep track of unrecognized or invalid storage locations
/// Because they affect our decisions for recognized locations,
/// be it nested conflict or merging out of scope accesses.
/// The access map is just a “cache” of accesses.
/// Keeping those invalid ones just makes the lookup faster
AccessMap accessMap;
/// Instruction pairs we can merge the scope of
MergeablePairs mergePairs;
/// 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:
LoopRegionFunctionInfo *LRFI;
AccessedStorageAnalysis *ASA;
Result result;
public:
AccessConflictAndMergeAnalysis(LoopRegionFunctionInfo *LRFI,
AccessedStorageAnalysis *ASA)
: LRFI(LRFI), ASA(ASA) {}
void analyze();
const Result &getResult() { return result; }
protected:
void identifyBeginAccesses();
void
propagateAccessSetsBottomUp(LoopRegionToAccessedStorage &regionToStorageMap,
llvm::SmallVector<unsigned, 16> worklist);
void calcBottomUpOrder(llvm::SmallVectorImpl<unsigned> &worklist);
void visitBeginAccess(BeginAccessInst *beginAccess, RegionInfo &info);
void visitEndAccess(EndAccessInst *endAccess, RegionInfo &info);
void visitFullApply(FullApplySite fullApply, RegionInfo &info);
void visitMayRelease(SILInstruction *instr, RegionInfo &info);
void mergePredAccesses(LoopRegion *region,
RegionIDToLocalInfoMap &localRegionInfos);
void detectConflictsInLoop(LoopRegion *loopRegion,
RegionIDToLocalInfoMap &localRegionInfos,
LoopRegionToAccessedStorage &accessSetsOfRegions);
void localDataFlowInBlock(LoopRegion *bbRegion,
RegionIDToLocalInfoMap &localRegionInfos);
private:
void addInScopeAccess(RegionInfo &info, BeginAccessInst *beginAccess);
void removeInScopeAccess(RegionInfo &info, BeginAccessInst *beginAccess);
void recordConflict(RegionInfo &info, const AccessedStorage &storage);
void addOutOfScopeAccess(RegionInfo &info, BeginAccessInst *beginAccess);
void mergeAccessStruct(RegionInfo &info,
RegionInfo::AccessSummary &accessStruct,
const RegionInfo::AccessSummary &RHSAccessStruct);
void merge(RegionInfo &info, const RegionInfo &RHS);
void removeConflictFromStruct(RegionInfo &info,
RegionInfo::AccessSummary &accessStruct,
const AccessedStorage &storage, bool isInScope);
void visitSetForConflicts(
const DenseAccessSet &accessSet, RegionInfo &info,
AccessConflictAndMergeAnalysis::AccessedStorageSet &loopStorage);
void
detectApplyConflicts(const swift::FunctionAccessedStorage &callSiteAccesses,
const DenseAccessSet &conflictFreeSet,
const swift::FullApplySite &fullApply, RegionInfo &info);
void detectMayReleaseConflicts(const DenseAccessSet &conflictFreeSet,
SILInstruction *instr, RegionInfo &info);
};
} // namespace
void AccessConflictAndMergeAnalysis::addInScopeAccess(
RegionInfo &info, BeginAccessInst *beginAccess) {
assert(info.inScopeConflictFreeAccesses.conflictFreeAccesses.count(
beginAccess) == 0 &&
"the begin_access should not have been in Vec.");
info.inScopeConflictFreeAccesses.conflictFreeAccesses.insert(beginAccess);
}
void AccessConflictAndMergeAnalysis::removeInScopeAccess(
RegionInfo &info, BeginAccessInst *beginAccess) {
auto it = std::find(
info.inScopeConflictFreeAccesses.conflictFreeAccesses.begin(),
info.inScopeConflictFreeAccesses.conflictFreeAccesses.end(), beginAccess);
assert(it != info.inScopeConflictFreeAccesses.conflictFreeAccesses.end() &&
"the begin_access should have been in Vec.");
info.inScopeConflictFreeAccesses.conflictFreeAccesses.erase(it);
}
void AccessConflictAndMergeAnalysis::removeConflictFromStruct(
RegionInfo &info, RegionInfo::AccessSummary &accessStruct,
const AccessedStorage &storage, bool isInScope) {
auto pred = [&](BeginAccessInst *it) {
auto &currStorage = result.getAccessInfo(it);
return !currStorage.isDistinctFrom(storage);
};
auto it = std::find_if(accessStruct.conflictFreeAccesses.begin(),
accessStruct.conflictFreeAccesses.end(), pred);
while (it != accessStruct.conflictFreeAccesses.end()) {
if (isInScope) {
auto &ai = result.getAccessInfo(*it);
ai.setSeenNestedConflict();
}
accessStruct.conflictFreeAccesses.erase(it);
it = std::find_if(accessStruct.conflictFreeAccesses.begin(),
accessStruct.conflictFreeAccesses.end(), pred);
}
}
void AccessConflictAndMergeAnalysis::recordConflict(
RegionInfo &info, const AccessedStorage &storage) {
removeConflictFromStruct(info, info.outOfScopeConflictFreeAccesses, storage,
false /*isInScope*/);
removeConflictFromStruct(info, info.inScopeConflictFreeAccesses, storage,
true /*isInScope*/);
}
void AccessConflictAndMergeAnalysis::addOutOfScopeAccess(
RegionInfo &info, BeginAccessInst *beginAccess) {
auto newStorageInfo = result.getAccessInfo(beginAccess);
auto pred = [&](BeginAccessInst *it) {
auto currStorageInfo = result.getAccessInfo(it);
return currStorageInfo.hasIdenticalBase(newStorageInfo);
};
auto it = std::find_if(
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.rbegin(),
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.rend(), pred);
if (it == info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.rend()) {
// We don't have a match in outOfScopeConflictFreeAccesses
// Just add it and return
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.insert(
beginAccess);
return;
}
auto *otherBegin = *it;
auto rmIt = std::find(
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.begin(),
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.end(),
otherBegin);
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.erase(rmIt);
auto predDistinct = [&](BeginAccessInst *it) {
auto currStorageInfo = result.getAccessInfo(it);
return !currStorageInfo.isDistinctFrom(newStorageInfo);
};
auto itDistinct = std::find_if(
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.begin(),
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.end(),
predDistinct);
if (itDistinct ==
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.end()) {
LLVM_DEBUG(llvm::dbgs() << "Found mergable pair: " << *otherBegin << ", "
<< *beginAccess << "\n");
result.mergePairs.push_back(std::make_pair(otherBegin, beginAccess));
} else {
while (itDistinct !=
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.end()) {
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.erase(
itDistinct);
itDistinct = std::find_if(
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.begin(),
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.end(),
predDistinct);
}
}
info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.insert(beginAccess);
}
void AccessConflictAndMergeAnalysis::mergeAccessStruct(
RegionInfo &info, RegionInfo::AccessSummary &accessStruct,
const RegionInfo::AccessSummary &RHSAccessStruct) {
if (!accessStruct.merged) {
accessStruct.conflictFreeAccesses.insert(
RHSAccessStruct.conflictFreeAccesses.begin(),
RHSAccessStruct.conflictFreeAccesses.end());
accessStruct.merged = true;
return;
}
auto pred = [&](BeginAccessInst *it) {
return RHSAccessStruct.conflictFreeAccesses.count(it) == 0;
};
accessStruct.conflictFreeAccesses.remove_if(pred);
}
void AccessConflictAndMergeAnalysis::merge(RegionInfo &info,
const RegionInfo &RHS) {
info.unidentifiedAccess |= RHS.unidentifiedAccess;
mergeAccessStruct(info, info.inScopeConflictFreeAccesses,
RHS.inScopeConflictFreeAccesses);
mergeAccessStruct(info, info.outOfScopeConflictFreeAccesses,
RHS.outOfScopeConflictFreeAccesses);
}
// Top-level driver for AccessConflictAndMergeAnalysis
void AccessConflictAndMergeAnalysis::analyze() {
identifyBeginAccesses();
LoopRegionToAccessedStorage accessSetsOfRegions;
llvm::SmallVector<unsigned, 16> worklist;
calcBottomUpOrder(worklist);
propagateAccessSetsBottomUp(accessSetsOfRegions, worklist);
LLVM_DEBUG(llvm::dbgs() << "Processing Function: "
<< LRFI->getFunction()->getName() << "\n");
while (!worklist.empty()) {
auto regionID = worklist.pop_back_val();
LLVM_DEBUG(llvm::dbgs() << "Processing Sub-Region: " << regionID << "\n");
auto *region = LRFI->getRegion(regionID);
RegionIDToLocalInfoMap localRegionInfos;
// This is RPO order of the sub-regions
for (auto subID : region->getSubregions()) {
auto *subRegion = LRFI->getRegion(subID);
// testIrreducibleGraph2 in test/SILOptimizer/access_enforcement_opts:
// If the sub-region is the source of a previously visited backedge,
// Then the in-state is an empty set.
bool disableCrossBlock = false;
if (localRegionInfos.find(subID) != localRegionInfos.end()) {
// Irreducible loop - we already set the predecessor to empty set
disableCrossBlock = true;
} else {
localRegionInfos.insert(
std::make_pair(subID, RegionInfo(result.accessMap.size())));
mergePredAccesses(subRegion, localRegionInfos);
}
if (subRegion->isBlock()) {
localDataFlowInBlock(subRegion, localRegionInfos);
} else {
assert(subRegion->isLoop() && "Expected a loop sub-region");
detectConflictsInLoop(subRegion, localRegionInfos, accessSetsOfRegions);
}
// After doing the control flow on the region, and as mentioned above,
// the sub-region is the source of a previously visited backedge,
// we want to remove the merging candidates from its final state
if (disableCrossBlock) {
// Clear-out the out state: this is risky irreducible control flow
// Only in-block conflict and merging is allowed
localRegionInfos.find(subID)->getSecond().reset();
}
}
}
}
// 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.
//
// Also, add the storage location to the function's RegionStorage
//
// 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 AccessConflictAndMergeAnalysis::identifyBeginAccesses() {
for (auto &BB : *LRFI->getFunction()) {
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());
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());
}
}
}
// Returns a mapping from each loop sub-region to all its access storage
// Propagates access summaries bottom-up from nested regions
void AccessConflictAndMergeAnalysis::propagateAccessSetsBottomUp(
LoopRegionToAccessedStorage &regionToStorageMap,
llvm::SmallVector<unsigned, 16> worklist) {
while (!worklist.empty()) {
auto regionID = worklist.pop_back_val();
auto *region = LRFI->getRegion(regionID);
assert(regionToStorageMap.find(regionID) == regionToStorageMap.end() &&
"Should not process a region twice");
AccessedStorageSet &accessedStorageSet = regionToStorageMap[regionID];
for (auto subID : region->getSubregions()) {
auto *subRegion = LRFI->getRegion(subID);
if (subRegion->isLoop()) {
// propagate access summaries bottom-up from nested loops.
auto subRegionStorageIt = regionToStorageMap.find(subID);
assert(subRegionStorageIt != regionToStorageMap.end() &&
"Should have processed sub-region");
for (auto storage : subRegionStorageIt->getSecond()) {
accessedStorageSet.insert(storage);
}
} else {
assert(subRegion->isBlock() && "Expected a block region");
auto *bb = subRegion->getBlock();
for (auto &instr : *bb) {
if (auto *beginAccess = dyn_cast<BeginAccessInst>(&instr)) {
const AccessedStorage &storage =
findAccessedStorageNonNested(beginAccess->getSource());
accessedStorageSet.insert(storage);
}
if (auto *beginAccess = dyn_cast<BeginUnpairedAccessInst>(&instr)) {
const AccessedStorage &storage =
findAccessedStorageNonNested(beginAccess->getSource());
accessedStorageSet.insert(storage);
}
}
}
}
}
}
// Helper function for calcBottomUpOrder
static void calcBottomUpOrderRecurse(LoopRegion *region,
llvm::SmallVectorImpl<unsigned> &worklist,
LoopRegionFunctionInfo *LRFI) {
worklist.push_back(region->getID());
for (auto regionIndex : region->getReverseSubregions()) {
auto *region = LRFI->getRegion(regionIndex);
if (region->isBlock())
continue;
calcBottomUpOrderRecurse(region, worklist, LRFI);
}
}
// Returns a worklist of loop IDs is bottom-up order.
void AccessConflictAndMergeAnalysis::calcBottomUpOrder(
llvm::SmallVectorImpl<unsigned> &worklist) {
auto *topRegion = LRFI->getTopLevelRegion();
calcBottomUpOrderRecurse(topRegion, worklist, LRFI);
}
void AccessConflictAndMergeAnalysis::visitBeginAccess(
BeginAccessInst *beginAccess, RegionInfo &info) {
if (beginAccess->getEnforcement() != SILAccessEnforcement::Dynamic)
return;
// Get the Access info:
auto &beginAccessInfo = result.getAccessInfo(beginAccess);
if (beginAccessInfo.getKind() == AccessedStorage::Unidentified) {
info.unidentifiedAccess = true;
}
SILAccessKind beginAccessKind = beginAccess->getAccessKind();
// check the current in-scope accesses for conflicts:
bool changed = false;
do {
changed = false;
for (auto *outerBeginAccess : info.getInScopeAccesses()) {
// If both are reads, keep the mapped access.
if (!accessKindMayConflict(beginAccessKind,
outerBeginAccess->getAccessKind())) {
continue;
}
auto &outerAccessInfo = result.getAccessInfo(outerBeginAccess);
// If there is no potential conflict, leave the outer access mapped.
if (outerAccessInfo.isDistinctFrom(beginAccessInfo))
continue;
LLVM_DEBUG(beginAccessInfo.dump();
llvm::dbgs() << " may conflict with:\n";
outerAccessInfo.dump());
recordConflict(info, outerAccessInfo);
changed = true;
break;
}
} while (changed);
// Record the current access to InScopeAccesses.
// It can potentially be folded
// regardless of whether it may conflict with an outer access.
addInScopeAccess(info, beginAccess);
// We can merge out-of-scope regardless of having a conflict within a scope,
// normally, it would have made more sense to add it to out-of-scope set
// *only* after encountering the end_access instruction.
// However, that will lose us some valid optimization potential:
// consider the following pseudo-SIL:
// begin_access %x
// end_access %x
// begin_access %x
// conflict
// end_access %x
// we can merge both of these scopes
// but, if we only add the instr. after seeing end_access,
// then we would not have the first begin_access in out-of-scope
// set when encoutnering the 2nd end_access due to "conflict"
// NOTE: What we really want to do here is to check if
// we should add the new beginAccess to 'mergePairs' structure
// the reason for calling this method is to check for that.
// logically, we only need to add an instructio to
// out-of-scope conflict-free set when we visit end_access
addOutOfScopeAccess(info, beginAccess);
}
void AccessConflictAndMergeAnalysis::visitEndAccess(EndAccessInst *endAccess,
RegionInfo &info) {
auto *beginAccess = endAccess->getBeginAccess();
if (beginAccess->getEnforcement() != SILAccessEnforcement::Dynamic)
return;
auto &inScope = info.getInScopeAccesses();
auto it = std::find(inScope.begin(), inScope.end(), beginAccess);
if (it != inScope.end()) {
LLVM_DEBUG(llvm::dbgs() << "No conflict on one path from " << *beginAccess
<< " to " << *endAccess);
removeInScopeAccess(info, beginAccess);
}
// If this exact instruction is already in out-of-scope - skip:
if (info.outOfScopeConflictFreeAccesses.conflictFreeAccesses.count(
beginAccess) > 0) {
return;
}
// Else we have the opposite situation to the one described in
// visitBeginAccess: the first scope is the one conflicting while the second
// does not - begin_access %x conflict end_access %x begin_access %x
// end_access %x
// when seeing the conflict we remove the first begin instruction
// but, we can still merge those scopes *UNLESS* there's a conflict
// between the first end_access and the second begin_access
LLVM_DEBUG(llvm::dbgs() << "Got out of scope from " << *beginAccess << " to "
<< *endAccess << "\n");
addOutOfScopeAccess(info, beginAccess);
}
void AccessConflictAndMergeAnalysis::detectApplyConflicts(
const swift::FunctionAccessedStorage &callSiteAccesses,
const DenseAccessSet &conflictFreeSet,
const swift::FullApplySite &fullApply, RegionInfo &info) {
bool changed = false;
do {
changed = false;
for (auto *outerBeginAccess : conflictFreeSet) {
// If there is no potential conflict, leave the outer access mapped.
SILAccessKind accessKind = outerBeginAccess->getAccessKind();
AccessInfo &outerAccessInfo = result.getAccessInfo(outerBeginAccess);
if (!callSiteAccesses.mayConflictWith(accessKind, outerAccessInfo))
continue;
LLVM_DEBUG(
llvm::dbgs() << *fullApply.getInstruction() << " call site access: ";
callSiteAccesses.dump(); llvm::dbgs() << " may conflict with:\n";
outerAccessInfo.dump());
recordConflict(info, outerAccessInfo);
changed = true;
break;
}
} while (changed);
}
void AccessConflictAndMergeAnalysis::visitFullApply(FullApplySite fullApply,
RegionInfo &info) {
FunctionAccessedStorage callSiteAccesses;
ASA->getCallSiteEffects(callSiteAccesses, fullApply);
detectApplyConflicts(callSiteAccesses, info.getInScopeAccesses(), fullApply,
info);
detectApplyConflicts(callSiteAccesses, info.getOutOfScopeAccesses(),
fullApply, info);
}
void AccessConflictAndMergeAnalysis::detectMayReleaseConflicts(
const DenseAccessSet &conflictFreeSet, SILInstruction *instr,
RegionInfo &info) {
// TODO Introduce "Pure Swift" deinitializers
// We can then make use of alias information for instr's operands
// If they don't alias - we might get away with not recording a conflict
bool changed = false;
do {
changed = false;
for (auto *outerBeginAccess : conflictFreeSet) {
// Only class and global access that may alias would conflict
AccessInfo &outerAccessInfo = result.getAccessInfo(outerBeginAccess);
const AccessedStorage::Kind outerKind = outerAccessInfo.getKind();
if (outerKind != AccessedStorage::Class &&
outerKind != AccessedStorage::Global) {
continue;
}
// We can't prove what the deinitializer might do
// TODO Introduce "Pure Swift" deinitializers
LLVM_DEBUG(llvm::dbgs() << "MayRelease Instruction: " << *instr
<< " may conflict with:\n";
outerAccessInfo.dump());
recordConflict(info, outerAccessInfo);
changed = true;
break;
}
} while (changed);
}
void AccessConflictAndMergeAnalysis::visitMayRelease(SILInstruction *instr,
RegionInfo &info) {
detectMayReleaseConflicts(info.getInScopeAccesses(), instr, info);
detectMayReleaseConflicts(info.getOutOfScopeAccesses(), instr, info);
}
void AccessConflictAndMergeAnalysis::mergePredAccesses(
LoopRegion *region, RegionIDToLocalInfoMap &localRegionInfos) {
RegionInfo &info = localRegionInfos.find(region->getID())->getSecond();
auto bbRegionParentID = region->getParentID();
bool changed = false;
for (auto pred : region->getPreds()) {
auto *predRegion = LRFI->getRegion(pred);
assert((predRegion->getParentID() == bbRegionParentID) &&
"predecessor is not part of the parent region - unhandled control "
"flow");
(void)predRegion;
(void)bbRegionParentID;
if (localRegionInfos.find(pred) == localRegionInfos.end()) {
// Backedge / irreducable control flow - bail
info.reset();
// Clear out the accesses of all predecessor:
for (auto pred : region->getPreds()) {
if (localRegionInfos.find(pred) == localRegionInfos.end()) {
// Create a region info with empty-set for predecessors
localRegionInfos.insert(
std::make_pair(pred, RegionInfo(result.accessMap.size())));
}
RegionInfo &predInfo = localRegionInfos.find(pred)->getSecond();
predInfo.reset();
}
return;
}
const RegionInfo &predInfo = localRegionInfos.find(pred)->getSecond();
changed = true;
merge(info, predInfo);
}
if (!changed) {
// If there are no predecessors
info.reset();
return;
}
}
void AccessConflictAndMergeAnalysis::visitSetForConflicts(
const DenseAccessSet &accessSet, RegionInfo &info,
AccessConflictAndMergeAnalysis::AccessedStorageSet &loopStorage) {
bool changed = false;
do {
changed = false;
for (BeginAccessInst *beginAccess : accessSet) {
AccessInfo &accessInfo = result.getAccessInfo(beginAccess);
for (auto loopAccess : loopStorage) {
if (loopAccess.isDistinctFrom(accessInfo) && !info.unidentifiedAccess)
continue;
recordConflict(info, loopAccess);
changed = true;
break;
}
if (changed)
break;
}
} while (changed);
}
void AccessConflictAndMergeAnalysis::detectConflictsInLoop(
LoopRegion *loopRegion, RegionIDToLocalInfoMap &localRegionInfos,
LoopRegionToAccessedStorage &accessSetsOfRegions) {
assert(loopRegion->isLoop() && "Expected a loop region");
auto loopID = loopRegion->getID();
RegionInfo &info = localRegionInfos.find(loopID)->getSecond();
AccessedStorageSet &loopStorage =
accessSetsOfRegions.find(loopID)->getSecond();
visitSetForConflicts(info.getInScopeAccesses(), info, loopStorage);
visitSetForConflicts(info.getOutOfScopeAccesses(), info, loopStorage);
}
void AccessConflictAndMergeAnalysis::localDataFlowInBlock(
LoopRegion *bbRegion, RegionIDToLocalInfoMap &localRegionInfos) {
assert(bbRegion->isBlock() && "Expected a block region");
auto *bb = bbRegion->getBlock();
RegionInfo &info = localRegionInfos.find(bbRegion->getID())->getSecond();
for (auto &instr : *bb) {
if (auto *beginAccess = dyn_cast<BeginAccessInst>(&instr)) {
visitBeginAccess(beginAccess, info);
continue;
}
if (auto *endAccess = dyn_cast<EndAccessInst>(&instr)) {
visitEndAccess(endAccess, info);
continue;
}
if (auto fullApply = FullApplySite::isa(&instr)) {
visitFullApply(fullApply, info);
continue;
}
if (instr.mayRelease()) {
visitMayRelease(&instr, info);
}
}
}
// -----------------------------------------------------------------------------
// 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(AccessConflictAndMergeAnalysis::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;
LLVM_DEBUG(llvm::dbgs() << "Folding " << *beginAccess);
}
return changed;
}
/// Perform local access marker elimination.
///
/// Disable access checks for 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 AccessConflictAndMergeAnalysis::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)) {
LLVM_DEBUG(llvm::dbgs() << "Disabling dead access " << *beginAccess);
beginAccess->setEnforcement(SILAccessEnforcement::Static);
changed = true;
}
}
return changed;
}
// TODO: support multi-end access cases
static EndAccessInst *getSingleEndAccess(BeginAccessInst *inst) {
EndAccessInst *end = nullptr;
for (auto *currEnd : inst->getEndAccesses()) {
if (end == nullptr)
end = currEnd;
else
return nullptr;
}
return end;
}
struct SCCInfo {
unsigned id;
bool hasLoop;
};
static void mergeEndAccesses(BeginAccessInst *parentIns,
BeginAccessInst *childIns) {
auto *endP = getSingleEndAccess(parentIns);
if (!endP)
llvm_unreachable("not supported");
auto *endC = getSingleEndAccess(childIns);
if (!endC)
llvm_unreachable("not supported");
endC->setOperand(parentIns);
endP->eraseFromParent();
}
static bool canMergeEnd(BeginAccessInst *parentIns, BeginAccessInst *childIns) {
auto *endP = getSingleEndAccess(parentIns);
if (!endP)
return false;
auto *endC = getSingleEndAccess(childIns);
if (!endC)
return false;
return true;
}
// TODO: support other merge patterns
static bool
canMergeBegin(PostDominanceInfo *postDomTree,
const llvm::DenseMap<SILBasicBlock *, SCCInfo> &blockToSCCMap,
BeginAccessInst *parentIns, BeginAccessInst *childIns) {
if (!postDomTree->properlyDominates(childIns, parentIns)) {
return false;
}
auto parentSCCIt = blockToSCCMap.find(parentIns->getParent());
assert(parentSCCIt != blockToSCCMap.end() && "Expected block in SCC Map");
auto childSCCIt = blockToSCCMap.find(childIns->getParent());
assert(childSCCIt != blockToSCCMap.end() && "Expected block in SCC Map");
auto parentSCC = parentSCCIt->getSecond();
auto childSCC = childSCCIt->getSecond();
if (parentSCC.id == childSCC.id) {
return true;
}
if (parentSCC.hasLoop) {
return false;
}
if (childSCC.hasLoop) {
return false;
}
return true;
}
static bool
canMerge(PostDominanceInfo *postDomTree,
const llvm::DenseMap<SILBasicBlock *, SCCInfo> &blockToSCCMap,
BeginAccessInst *parentIns, BeginAccessInst *childIns) {
if (!canMergeBegin(postDomTree, blockToSCCMap, parentIns, childIns))
return false;
return canMergeEnd(parentIns, childIns);
}
/// Perform access merging.
static bool mergeAccesses(
SILFunction *F, PostDominanceInfo *postDomTree,
const AccessConflictAndMergeAnalysis::MergeablePairs &mergePairs) {
bool changed = false;
// Compute a map from each block to its SCC -
// For now we can't merge cross SCC boundary
llvm::DenseMap<SILBasicBlock *, SCCInfo> blockToSCCMap;
SCCInfo info;
info.id = 0;
for (auto sccIt = scc_begin(F); !sccIt.isAtEnd(); ++sccIt) {
++info.id;
info.hasLoop = sccIt.hasLoop();
for (auto *bb : *sccIt) {
blockToSCCMap.insert(std::make_pair(bb, info));
}
}
// make a temporary reverse copy to work on:
// It is in reverse order just to make it easier to debug / follow
AccessConflictAndMergeAnalysis::MergeablePairs workPairs;
workPairs.append(mergePairs.rbegin(), mergePairs.rend());
// Assume the result contains two access pairs to be merged:
// (begin_access %1, begin_access %2)
// = merge end_access %1 with begin_access %2
// (begin_access %2, begin_access %3)
// = merge end_access %2 with begin_access %3
// After merging the first pair, begin_access %2 is removed,
// so the second pair in the result list points to a to-be-deleted
// begin_access instruction. We store (begin_access %2 -> begin_access %1)
// to re-map a merged begin_access to it's replaced instruction.
llvm::DenseMap<BeginAccessInst *, BeginAccessInst *> oldToNewMap;
while (!workPairs.empty()) {
auto curr = workPairs.pop_back_val();
auto *parentIns = curr.first;
auto *childIns = curr.second;
if (oldToNewMap.count(parentIns) != 0) {
parentIns = oldToNewMap[parentIns];
}
assert(oldToNewMap.count(childIns) == 0 &&
"Can't have same child instruction twice in map");
// The optimization might not currently support every mergeable pair
// If the current pattern is not supported - skip
if (!canMerge(postDomTree, blockToSCCMap, parentIns, childIns))
continue;
LLVM_DEBUG(llvm::dbgs()
<< "Merging: " << *childIns << " into " << *parentIns << "\n");
// Change the type of access of parent:
// should be the worse between it and child
auto childAccess = childIns->getAccessKind();
if (parentIns->getAccessKind() < childAccess) {
parentIns->setAccessKind(childAccess);
}
// Change the no nested conflict of parent:
// should be the worst case scenario: we might merge to non-conflicting
// scopes to a conflicting one. f the new result does not conflict,
// a later on pass will remove the flag
parentIns->setNoNestedConflict(false);
// remove end accesses and create new ones that cover bigger scope:
mergeEndAccesses(parentIns, childIns);
// In case the child instruction is at the map,
// updated the oldToNewMap to reflect that we are getting rid of it:
oldToNewMap.insert(std::make_pair(childIns, parentIns));
// Modify the users of child instruction to use the parent:
childIns->replaceAllUsesWith(parentIns);
changed = true;
}
// Delete all old instructions from parent scopes:
while (!oldToNewMap.empty()) {
auto curr = oldToNewMap.begin();
auto *oldIns = curr->getFirst();
oldToNewMap.erase(oldIns);
oldIns->eraseFromParent();
}
return changed;
}
namespace {
struct AccessEnforcementOpts : public SILFunctionTransform {
void run() override {
SILFunction *F = getFunction();
if (F->empty())
return;
LLVM_DEBUG(llvm::dbgs() << "Running local AccessEnforcementOpts on "
<< F->getName() << "\n");
LoopRegionFunctionInfo *LRFI = getAnalysis<LoopRegionAnalysis>()->get(F);
AccessedStorageAnalysis *ASA = getAnalysis<AccessedStorageAnalysis>();
AccessConflictAndMergeAnalysis a(LRFI, ASA);
a.analyze();
auto result = a.getResult();
// Perform access folding by setting the [no_nested_conflict] flag on
// begin_access instructions.
if (foldNonNestedAccesses(result.accessMap)) {
// 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);
// Perform the access merging
// The inital version of the optimization requires a postDomTree
PostDominanceAnalysis *postDomAnalysis =
getAnalysis<PostDominanceAnalysis>();
PostDominanceInfo *postDomTree = postDomAnalysis->get(F);
if (mergeAccesses(F, postDomTree, result.mergePairs))
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}
};
} // namespace
SILTransform *swift::createAccessEnforcementOpts() {
return new AccessEnforcementOpts();
}