Skip to content

AliasAnalysis: check for immutable scopes. #37473

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions include/swift/Basic/ValueEnumerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,20 @@

namespace swift {

typedef unsigned ValueIndexTy;

/// / This class maps values to unique indices.
template<class ValueTy, class IndexTy = size_t>
template<class ValueTy>
class ValueEnumerator {
/// A running counter to enumerate values.
IndexTy counter = 0;
ValueIndexTy counter = 0;

/// Maps values to unique integers.
llvm::DenseMap<ValueTy, IndexTy> ValueToIndex;
llvm::DenseMap<ValueTy, ValueIndexTy> ValueToIndex;

public:
/// Return the index of value \p v.
IndexTy getIndex(const ValueTy &v) {
ValueIndexTy getIndex(const ValueTy &v) {
// Return the index of this Key, if we've assigned one already.
auto It = ValueToIndex.find(v);
if (It != ValueToIndex.end()) {
Expand All @@ -38,6 +40,7 @@ class ValueEnumerator {

// Generate a new counter for the key.
ValueToIndex[v] = ++counter;
assert(counter != 0 && "counter overflow in ValueEnumerator");
return counter;
}

Expand Down
6 changes: 3 additions & 3 deletions include/swift/SIL/OwnershipUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -1076,10 +1076,10 @@ void findTransitiveReborrowBaseValuePairs(
BorrowingOperand initialScopeOperand, SILValue origBaseValue,
function_ref<void(SILPhiArgument *, SILValue)> visitReborrowBaseValuePair);

/// Given a begin_borrow visit all end_borrow users of the borrow or its
/// reborrows.
/// Given a begin of a borrow scope, visit all end_borrow users of the borrow or
/// its reborrows.
void visitTransitiveEndBorrows(
BeginBorrowInst *borrowInst,
BorrowedValue beginBorrow,
function_ref<void(EndBorrowInst *)> visitEndBorrow);

} // namespace swift
Expand Down
43 changes: 31 additions & 12 deletions include/swift/SILOptimizer/Analysis/AliasAnalysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,26 @@ namespace {
/// A key used for the AliasAnalysis cache.
///
/// This struct represents the argument list to the method 'alias'. The two
/// SILValue pointers are mapped to size_t indices because we need an
/// SILValue pointers are mapped to integer indices because we need an
/// efficient way to invalidate them (the mechanism is described below). The
/// Type arguments are translated to void* because their underlying storage is
/// opaque pointers that never goes away.
struct AliasKeyTy {
// The SILValue pair:
size_t V1, V2;
swift::ValueIndexTy V1, V2;
// The TBAAType pair:
void *T1, *T2;
};

/// A key used for the MemoryBehavior Analysis cache.
///
/// The two SILValue pointers are mapped to size_t indices because we need an
/// The two SILValue pointers are mapped to integer indices because we need an
/// efficient way to invalidate them (the mechanism is described below). The
/// RetainObserveKind represents the inspection mode for the memory behavior
/// analysis.
struct MemBehaviorKeyTy {
// The SILValue pair:
size_t V1, V2;
swift::ValueIndexTy V1, V2;
};
}

Expand Down Expand Up @@ -113,6 +113,19 @@ class AliasAnalysis : public SILAnalysis {
/// The computeMemoryBehavior() method uses this map to cache queries.
llvm::DenseMap<MemBehaviorKeyTy, MemoryBehavior> MemoryBehaviorCache;

/// Set of instructions inside immutable-scopes.
///
/// Contains pairs of intruction indices: the first instruction is the begin-
/// scope instruction (e.g. begin_access), the second instruction is an
/// instruction inside the scope (only may-write instructions are considered).
llvm::DenseSet<MemBehaviorKeyTy> instsInImmutableScopes;

/// Computed immutable scopes.
///
/// Contains the begin-scope instruction's indices (e.g. begin_access) of
/// all computed scopes.
llvm::DenseSet<ValueIndexTy> immutableScopeComputed;

/// The caches can't directly map a pair of value/instruction pointers
/// to results because we'd like to be able to remove deleted instruction
/// pointers without having to scan the whole map. So, instead of storing
Expand All @@ -135,6 +148,10 @@ class AliasAnalysis : public SILAnalysis {

virtual bool needsNotifications() override { return true; }

void computeImmutableScope(SingleValueInstruction *beginScopeInst,
ValueIndexTy beginIdx);

bool isInImmutableScope(SILInstruction *inst, SILValue V);

public:
AliasAnalysis(SILModule *M)
Expand Down Expand Up @@ -246,6 +263,8 @@ class AliasAnalysis : public SILAnalysis {
MemoryBehaviorCache.clear();
InstructionToIndex.clear();
ValueToIndex.clear();
instsInImmutableScopes.clear();
immutableScopeComputed.clear();
}

virtual void invalidate(SILFunction *,
Expand Down Expand Up @@ -276,17 +295,17 @@ SILType computeTBAAType(SILValue V);
namespace llvm {
template <> struct DenseMapInfo<AliasKeyTy> {
static inline AliasKeyTy getEmptyKey() {
auto Allone = std::numeric_limits<size_t>::max();
auto Allone = std::numeric_limits<swift::ValueIndexTy>::max();
return {0, Allone, nullptr, nullptr};
}
static inline AliasKeyTy getTombstoneKey() {
auto Allone = std::numeric_limits<size_t>::max();
auto Allone = std::numeric_limits<swift::ValueIndexTy>::max();
return {Allone, 0, nullptr, nullptr};
}
static unsigned getHashValue(const AliasKeyTy Val) {
unsigned H = 0;
H ^= DenseMapInfo<size_t>::getHashValue(Val.V1);
H ^= DenseMapInfo<size_t>::getHashValue(Val.V2);
H ^= DenseMapInfo<swift::ValueIndexTy>::getHashValue(Val.V1);
H ^= DenseMapInfo<swift::ValueIndexTy>::getHashValue(Val.V2);
H ^= DenseMapInfo<void *>::getHashValue(Val.T1);
H ^= DenseMapInfo<void *>::getHashValue(Val.T2);
return H;
Expand All @@ -301,17 +320,17 @@ namespace llvm {

template <> struct DenseMapInfo<MemBehaviorKeyTy> {
static inline MemBehaviorKeyTy getEmptyKey() {
auto Allone = std::numeric_limits<size_t>::max();
auto Allone = std::numeric_limits<swift::ValueIndexTy>::max();
return {0, Allone};
}
static inline MemBehaviorKeyTy getTombstoneKey() {
auto Allone = std::numeric_limits<size_t>::max();
auto Allone = std::numeric_limits<swift::ValueIndexTy>::max();
return {Allone, 0};
}
static unsigned getHashValue(const MemBehaviorKeyTy V) {
unsigned H = 0;
H ^= DenseMapInfo<size_t>::getHashValue(V.V1);
H ^= DenseMapInfo<size_t>::getHashValue(V.V2);
H ^= DenseMapInfo<swift::ValueIndexTy>::getHashValue(V.V1);
H ^= DenseMapInfo<swift::ValueIndexTy>::getHashValue(V.V2);
return H;
}
static bool isEqual(const MemBehaviorKeyTy LHS,
Expand Down
4 changes: 2 additions & 2 deletions lib/SIL/Utils/OwnershipUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1327,10 +1327,10 @@ void swift::findTransitiveReborrowBaseValuePairs(
}

void swift::visitTransitiveEndBorrows(
BeginBorrowInst *borrowInst,
BorrowedValue beginBorrow,
function_ref<void(EndBorrowInst *)> visitEndBorrow) {
SmallSetVector<SILValue, 4> worklist;
worklist.insert(borrowInst);
worklist.insert(beginBorrow.value);

while (!worklist.empty()) {
auto val = worklist.pop_back_val();
Expand Down
8 changes: 4 additions & 4 deletions lib/SILOptimizer/Analysis/AliasAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -818,11 +818,11 @@ SILAnalysis *swift::createAliasAnalysis(SILModule *M) {

AliasKeyTy AliasAnalysis::toAliasKey(SILValue V1, SILValue V2,
SILType Type1, SILType Type2) {
size_t idx1 = ValueToIndex.getIndex(V1);
assert(idx1 != std::numeric_limits<size_t>::max() &&
ValueIndexTy idx1 = ValueToIndex.getIndex(V1);
assert(idx1 != std::numeric_limits<ValueIndexTy>::max() &&
"~0 index reserved for empty/tombstone keys");
size_t idx2 = ValueToIndex.getIndex(V2);
assert(idx2 != std::numeric_limits<size_t>::max() &&
ValueIndexTy idx2 = ValueToIndex.getIndex(V2);
assert(idx2 != std::numeric_limits<ValueIndexTy>::max() &&
"~0 index reserved for empty/tombstone keys");
void *t1 = Type1.getOpaqueValue();
void *t2 = Type2.getOpaqueValue();
Expand Down
167 changes: 162 additions & 5 deletions lib/SILOptimizer/Analysis/MemoryBehavior.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/SILVisitor.h"
#include "swift/SIL/OwnershipUtils.h"
#include "swift/SIL/BasicBlockBits.h"
#include "swift/SILOptimizer/Analysis/AliasAnalysis.h"
#include "swift/SILOptimizer/Analysis/EscapeAnalysis.h"
#include "swift/SILOptimizer/Analysis/SideEffectAnalysis.h"
Expand All @@ -28,6 +30,11 @@ using namespace swift;
// memory usage of this cache.
static const int MemoryBehaviorAnalysisMaxCacheSize = 16384;

// The maximum size of instsInImmutableScopes.
// Usually more than enough. But in corner cases it can grow quadratically (in
// case of many large nested immutable scopes).
static const int ImmutableScopeInstsMaxSize = 18384;

//===----------------------------------------------------------------------===//
// Memory Behavior Implementation
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -536,21 +543,171 @@ AliasAnalysis::computeMemoryBehavior(SILInstruction *Inst, SILValue V) {
return Result;
}

/// If \p V is an address of an immutable memory, return the begin of the
/// scope where the memory can be considered to be immutable.
///
/// This is either a ``begin_access [read]`` in case V is the result of the
/// begin_access or a projection of it.
/// Or it is the begin of a borrow scope (begin_borrow, load_borrow, a
/// guaranteed function argument) of an immutable copy-on-write buffer.
/// For example:
/// %b = begin_borrow %array_buffer
/// %V = ref_element_addr [immutable] %b : $BufferType, #BufferType.someField
///
static SILValue getBeginScopeInst(SILValue V) {
SILValue accessScope = getAccessScope(V);
if (auto *access = dyn_cast<BeginAccessInst>(accessScope)) {
if (access->getAccessKind() == SILAccessKind::Read &&
access->getEnforcement() != SILAccessEnforcement::Unsafe)
return access;
return SILValue();
}
SILValue accessBase = getAccessBase(V);
SILValue object;
if (auto *elementAddr = dyn_cast<RefElementAddrInst>(accessBase)) {
if (!elementAddr->isImmutable())
return SILValue();
object = elementAddr->getOperand();
} else if (auto *tailAddr = dyn_cast<RefTailAddrInst>(accessBase)) {
if (!tailAddr->isImmutable())
return SILValue();
object = tailAddr->getOperand();
} else {
return SILValue();
}
if (BorrowedValue borrowedObj = getSingleBorrowIntroducingValue(object)) {
return borrowedObj.value;
}
return SILValue();
}

/// Collect all instructions which are inside an immutable scope.
///
/// The \p beginScopeInst is either a ``begin_access [read]`` or the begin of a
/// borrow scope (begin_borrow, load_borrow) of an immutable copy-on-write
/// buffer.
void AliasAnalysis::computeImmutableScope(SingleValueInstruction *beginScopeInst,
ValueIndexTy beginIdx) {
BasicBlockSet visitedBlocks(beginScopeInst->getFunction());
llvm::SmallVector<std::pair<SILInstruction *, SILBasicBlock *>, 16> workList;

auto addEndScopeInst = [&](SILInstruction *endScope) {
workList.push_back({endScope, endScope->getParent()});
bool isNew = visitedBlocks.insert(endScope->getParent());
(void)isNew;
assert(isNew);
};

// First step: add all scope-ending instructions to the worklist.
if (auto *beginAccess = dyn_cast<BeginAccessInst>(beginScopeInst)) {
for (EndAccessInst *endAccess : beginAccess->getEndAccesses()) {
addEndScopeInst(endAccess);
}
} else {
visitTransitiveEndBorrows(BorrowedValue(beginScopeInst), addEndScopeInst);
}

// Second step: walk up the control flow until the beginScopeInst and add
// all (potentially) memory writing instructions to instsInImmutableScopes.
while (!workList.empty()) {
auto instAndBlock = workList.pop_back_val();
SILBasicBlock *block = instAndBlock.second;
// If the worklist entry doesn't have an instruction, start at the end of
// the block.
auto iter = instAndBlock.first ? instAndBlock.first->getIterator()
: block->end();
// Walk up the instruction list - either to the begin of the block or until
// we hit the beginScopeInst.
while (true) {
if (iter == block->begin()) {
assert(block != block->getParent()->getEntryBlock() &&
"didn't find the beginScopeInst when walking up the CFG");
// Add all predecessor blocks to the worklist.
for (SILBasicBlock *pred : block->getPredecessorBlocks()) {
if (visitedBlocks.insert(pred))
workList.push_back({nullptr, pred});
}
break;
}
--iter;
SILInstruction *inst = &*iter;
if (inst == beginScopeInst) {
// When we are at the beginScopeInst we terminate the CFG walk.
break;
}
if (inst->mayWriteToMemory()) {
instsInImmutableScopes.insert({beginIdx,
InstructionToIndex.getIndex(inst)});
}
}
}
}

/// Returns true if \p inst is in an immutable scope of V.
///
/// That means that even if we don't know anything about inst, we can be sure
/// that inst cannot write to V.
/// An immutable scope is for example a read-only begin_access/end_access scope.
/// Another example is a borrow scope of an immutable copy-on-write buffer.
bool AliasAnalysis::isInImmutableScope(SILInstruction *inst, SILValue V) {
if (!V->getType().isAddress())
return false;

SILValue beginScope = getBeginScopeInst(V);
if (!beginScope)
return false;

if (auto *funcArg = dyn_cast<SILFunctionArgument>(beginScope)) {
// The immutable scope (= an guaranteed argument) spans over the whole
// function. We don't need to do any scope computation in this case.
assert(funcArg->getArgumentConvention().isGuaranteedConvention());
return true;
}

auto *beginScopeInst = dyn_cast<SingleValueInstruction>(beginScope);
if (!beginScopeInst)
return false;

ValueIndexTy beginIdx = InstructionToIndex.getIndex(beginScopeInst);

// Recompute the scope if not done yet.
if (immutableScopeComputed.insert(beginIdx).second) {
// In practice this will never happen. Just be be sure to not run into
// quadratic complexity in obscure corner cases.
if (instsInImmutableScopes.size() > ImmutableScopeInstsMaxSize)
return false;

computeImmutableScope(beginScopeInst, beginIdx);
}

ValueIndexTy instIdx = InstructionToIndex.getIndex(inst);
return instsInImmutableScopes.contains({beginIdx, instIdx});
}

MemBehavior
AliasAnalysis::computeMemoryBehaviorInner(SILInstruction *Inst, SILValue V) {
LLVM_DEBUG(llvm::dbgs() << "GET MEMORY BEHAVIOR FOR:\n " << *Inst << " "
<< *V);
assert(SEA && "SideEffectsAnalysis must be initialized!");
return MemoryBehaviorVisitor(this, SEA, EA, V).visit(Inst);

MemBehavior result = MemoryBehaviorVisitor(this, SEA, EA, V).visit(Inst);

// If the "regular" alias analysis thinks that Inst may modify V, check if
// Inst is in an immutable scope of V.
if (result > MemBehavior::MayRead && isInImmutableScope(Inst, V)) {
return (result == MemBehavior::MayWrite) ? MemBehavior::None
: MemBehavior::MayRead;
}
return result;
}

MemBehaviorKeyTy AliasAnalysis::toMemoryBehaviorKey(SILInstruction *V1,
SILValue V2) {
size_t idx1 = InstructionToIndex.getIndex(V1);
assert(idx1 != std::numeric_limits<size_t>::max() &&
ValueIndexTy idx1 = InstructionToIndex.getIndex(V1);
assert(idx1 != std::numeric_limits<ValueIndexTy>::max() &&
"~0 index reserved for empty/tombstone keys");
size_t idx2 = ValueToIndex.getIndex(V2);
assert(idx2 != std::numeric_limits<size_t>::max() &&
ValueIndexTy idx2 = ValueToIndex.getIndex(V2);
assert(idx2 != std::numeric_limits<ValueIndexTy>::max() &&
"~0 index reserved for empty/tombstone keys");
return {idx1, idx2};
}
Loading