Skip to content

Commit d001b1d

Browse files
authored
Merge pull request swiftlang#37473 from eeckstein/aliasanalysis-immutable-scopes
AliasAnalysis: check for immutable scopes.
2 parents fb2197c + 3701de4 commit d001b1d

File tree

9 files changed

+351
-43
lines changed

9 files changed

+351
-43
lines changed

include/swift/Basic/ValueEnumerator.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,20 @@
1818

1919
namespace swift {
2020

21+
typedef unsigned ValueIndexTy;
22+
2123
/// / This class maps values to unique indices.
22-
template<class ValueTy, class IndexTy = size_t>
24+
template<class ValueTy>
2325
class ValueEnumerator {
2426
/// A running counter to enumerate values.
25-
IndexTy counter = 0;
27+
ValueIndexTy counter = 0;
2628

2729
/// Maps values to unique integers.
28-
llvm::DenseMap<ValueTy, IndexTy> ValueToIndex;
30+
llvm::DenseMap<ValueTy, ValueIndexTy> ValueToIndex;
2931

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

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

include/swift/SIL/OwnershipUtils.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,10 +1076,10 @@ void findTransitiveReborrowBaseValuePairs(
10761076
BorrowingOperand initialScopeOperand, SILValue origBaseValue,
10771077
function_ref<void(SILPhiArgument *, SILValue)> visitReborrowBaseValuePair);
10781078

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

10851085
} // namespace swift

include/swift/SILOptimizer/Analysis/AliasAnalysis.h

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,26 @@ namespace {
2727
/// A key used for the AliasAnalysis cache.
2828
///
2929
/// This struct represents the argument list to the method 'alias'. The two
30-
/// SILValue pointers are mapped to size_t indices because we need an
30+
/// SILValue pointers are mapped to integer indices because we need an
3131
/// efficient way to invalidate them (the mechanism is described below). The
3232
/// Type arguments are translated to void* because their underlying storage is
3333
/// opaque pointers that never goes away.
3434
struct AliasKeyTy {
3535
// The SILValue pair:
36-
size_t V1, V2;
36+
swift::ValueIndexTy V1, V2;
3737
// The TBAAType pair:
3838
void *T1, *T2;
3939
};
4040

4141
/// A key used for the MemoryBehavior Analysis cache.
4242
///
43-
/// The two SILValue pointers are mapped to size_t indices because we need an
43+
/// The two SILValue pointers are mapped to integer indices because we need an
4444
/// efficient way to invalidate them (the mechanism is described below). The
4545
/// RetainObserveKind represents the inspection mode for the memory behavior
4646
/// analysis.
4747
struct MemBehaviorKeyTy {
4848
// The SILValue pair:
49-
size_t V1, V2;
49+
swift::ValueIndexTy V1, V2;
5050
};
5151
}
5252

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

116+
/// Set of instructions inside immutable-scopes.
117+
///
118+
/// Contains pairs of intruction indices: the first instruction is the begin-
119+
/// scope instruction (e.g. begin_access), the second instruction is an
120+
/// instruction inside the scope (only may-write instructions are considered).
121+
llvm::DenseSet<MemBehaviorKeyTy> instsInImmutableScopes;
122+
123+
/// Computed immutable scopes.
124+
///
125+
/// Contains the begin-scope instruction's indices (e.g. begin_access) of
126+
/// all computed scopes.
127+
llvm::DenseSet<ValueIndexTy> immutableScopeComputed;
128+
116129
/// The caches can't directly map a pair of value/instruction pointers
117130
/// to results because we'd like to be able to remove deleted instruction
118131
/// pointers without having to scan the whole map. So, instead of storing
@@ -135,6 +148,10 @@ class AliasAnalysis : public SILAnalysis {
135148

136149
virtual bool needsNotifications() override { return true; }
137150

151+
void computeImmutableScope(SingleValueInstruction *beginScopeInst,
152+
ValueIndexTy beginIdx);
153+
154+
bool isInImmutableScope(SILInstruction *inst, SILValue V);
138155

139156
public:
140157
AliasAnalysis(SILModule *M)
@@ -246,6 +263,8 @@ class AliasAnalysis : public SILAnalysis {
246263
MemoryBehaviorCache.clear();
247264
InstructionToIndex.clear();
248265
ValueToIndex.clear();
266+
instsInImmutableScopes.clear();
267+
immutableScopeComputed.clear();
249268
}
250269

251270
virtual void invalidate(SILFunction *,
@@ -276,17 +295,17 @@ SILType computeTBAAType(SILValue V);
276295
namespace llvm {
277296
template <> struct DenseMapInfo<AliasKeyTy> {
278297
static inline AliasKeyTy getEmptyKey() {
279-
auto Allone = std::numeric_limits<size_t>::max();
298+
auto Allone = std::numeric_limits<swift::ValueIndexTy>::max();
280299
return {0, Allone, nullptr, nullptr};
281300
}
282301
static inline AliasKeyTy getTombstoneKey() {
283-
auto Allone = std::numeric_limits<size_t>::max();
302+
auto Allone = std::numeric_limits<swift::ValueIndexTy>::max();
284303
return {Allone, 0, nullptr, nullptr};
285304
}
286305
static unsigned getHashValue(const AliasKeyTy Val) {
287306
unsigned H = 0;
288-
H ^= DenseMapInfo<size_t>::getHashValue(Val.V1);
289-
H ^= DenseMapInfo<size_t>::getHashValue(Val.V2);
307+
H ^= DenseMapInfo<swift::ValueIndexTy>::getHashValue(Val.V1);
308+
H ^= DenseMapInfo<swift::ValueIndexTy>::getHashValue(Val.V2);
290309
H ^= DenseMapInfo<void *>::getHashValue(Val.T1);
291310
H ^= DenseMapInfo<void *>::getHashValue(Val.T2);
292311
return H;
@@ -301,17 +320,17 @@ namespace llvm {
301320

302321
template <> struct DenseMapInfo<MemBehaviorKeyTy> {
303322
static inline MemBehaviorKeyTy getEmptyKey() {
304-
auto Allone = std::numeric_limits<size_t>::max();
323+
auto Allone = std::numeric_limits<swift::ValueIndexTy>::max();
305324
return {0, Allone};
306325
}
307326
static inline MemBehaviorKeyTy getTombstoneKey() {
308-
auto Allone = std::numeric_limits<size_t>::max();
327+
auto Allone = std::numeric_limits<swift::ValueIndexTy>::max();
309328
return {Allone, 0};
310329
}
311330
static unsigned getHashValue(const MemBehaviorKeyTy V) {
312331
unsigned H = 0;
313-
H ^= DenseMapInfo<size_t>::getHashValue(V.V1);
314-
H ^= DenseMapInfo<size_t>::getHashValue(V.V2);
332+
H ^= DenseMapInfo<swift::ValueIndexTy>::getHashValue(V.V1);
333+
H ^= DenseMapInfo<swift::ValueIndexTy>::getHashValue(V.V2);
315334
return H;
316335
}
317336
static bool isEqual(const MemBehaviorKeyTy LHS,

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,10 +1327,10 @@ void swift::findTransitiveReborrowBaseValuePairs(
13271327
}
13281328

13291329
void swift::visitTransitiveEndBorrows(
1330-
BeginBorrowInst *borrowInst,
1330+
BorrowedValue beginBorrow,
13311331
function_ref<void(EndBorrowInst *)> visitEndBorrow) {
13321332
SmallSetVector<SILValue, 4> worklist;
1333-
worklist.insert(borrowInst);
1333+
worklist.insert(beginBorrow.value);
13341334

13351335
while (!worklist.empty()) {
13361336
auto val = worklist.pop_back_val();

lib/SILOptimizer/Analysis/AliasAnalysis.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -818,11 +818,11 @@ SILAnalysis *swift::createAliasAnalysis(SILModule *M) {
818818

819819
AliasKeyTy AliasAnalysis::toAliasKey(SILValue V1, SILValue V2,
820820
SILType Type1, SILType Type2) {
821-
size_t idx1 = ValueToIndex.getIndex(V1);
822-
assert(idx1 != std::numeric_limits<size_t>::max() &&
821+
ValueIndexTy idx1 = ValueToIndex.getIndex(V1);
822+
assert(idx1 != std::numeric_limits<ValueIndexTy>::max() &&
823823
"~0 index reserved for empty/tombstone keys");
824-
size_t idx2 = ValueToIndex.getIndex(V2);
825-
assert(idx2 != std::numeric_limits<size_t>::max() &&
824+
ValueIndexTy idx2 = ValueToIndex.getIndex(V2);
825+
assert(idx2 != std::numeric_limits<ValueIndexTy>::max() &&
826826
"~0 index reserved for empty/tombstone keys");
827827
void *t1 = Type1.getOpaqueValue();
828828
void *t2 = Type2.getOpaqueValue();

lib/SILOptimizer/Analysis/MemoryBehavior.cpp

Lines changed: 162 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include "swift/SIL/InstructionUtils.h"
1616
#include "swift/SIL/MemAccessUtils.h"
1717
#include "swift/SIL/SILVisitor.h"
18+
#include "swift/SIL/OwnershipUtils.h"
19+
#include "swift/SIL/BasicBlockBits.h"
1820
#include "swift/SILOptimizer/Analysis/AliasAnalysis.h"
1921
#include "swift/SILOptimizer/Analysis/EscapeAnalysis.h"
2022
#include "swift/SILOptimizer/Analysis/SideEffectAnalysis.h"
@@ -28,6 +30,11 @@ using namespace swift;
2830
// memory usage of this cache.
2931
static const int MemoryBehaviorAnalysisMaxCacheSize = 16384;
3032

33+
// The maximum size of instsInImmutableScopes.
34+
// Usually more than enough. But in corner cases it can grow quadratically (in
35+
// case of many large nested immutable scopes).
36+
static const int ImmutableScopeInstsMaxSize = 18384;
37+
3138
//===----------------------------------------------------------------------===//
3239
// Memory Behavior Implementation
3340
//===----------------------------------------------------------------------===//
@@ -536,21 +543,171 @@ AliasAnalysis::computeMemoryBehavior(SILInstruction *Inst, SILValue V) {
536543
return Result;
537544
}
538545

546+
/// If \p V is an address of an immutable memory, return the begin of the
547+
/// scope where the memory can be considered to be immutable.
548+
///
549+
/// This is either a ``begin_access [read]`` in case V is the result of the
550+
/// begin_access or a projection of it.
551+
/// Or it is the begin of a borrow scope (begin_borrow, load_borrow, a
552+
/// guaranteed function argument) of an immutable copy-on-write buffer.
553+
/// For example:
554+
/// %b = begin_borrow %array_buffer
555+
/// %V = ref_element_addr [immutable] %b : $BufferType, #BufferType.someField
556+
///
557+
static SILValue getBeginScopeInst(SILValue V) {
558+
SILValue accessScope = getAccessScope(V);
559+
if (auto *access = dyn_cast<BeginAccessInst>(accessScope)) {
560+
if (access->getAccessKind() == SILAccessKind::Read &&
561+
access->getEnforcement() != SILAccessEnforcement::Unsafe)
562+
return access;
563+
return SILValue();
564+
}
565+
SILValue accessBase = getAccessBase(V);
566+
SILValue object;
567+
if (auto *elementAddr = dyn_cast<RefElementAddrInst>(accessBase)) {
568+
if (!elementAddr->isImmutable())
569+
return SILValue();
570+
object = elementAddr->getOperand();
571+
} else if (auto *tailAddr = dyn_cast<RefTailAddrInst>(accessBase)) {
572+
if (!tailAddr->isImmutable())
573+
return SILValue();
574+
object = tailAddr->getOperand();
575+
} else {
576+
return SILValue();
577+
}
578+
if (BorrowedValue borrowedObj = getSingleBorrowIntroducingValue(object)) {
579+
return borrowedObj.value;
580+
}
581+
return SILValue();
582+
}
583+
584+
/// Collect all instructions which are inside an immutable scope.
585+
///
586+
/// The \p beginScopeInst is either a ``begin_access [read]`` or the begin of a
587+
/// borrow scope (begin_borrow, load_borrow) of an immutable copy-on-write
588+
/// buffer.
589+
void AliasAnalysis::computeImmutableScope(SingleValueInstruction *beginScopeInst,
590+
ValueIndexTy beginIdx) {
591+
BasicBlockSet visitedBlocks(beginScopeInst->getFunction());
592+
llvm::SmallVector<std::pair<SILInstruction *, SILBasicBlock *>, 16> workList;
593+
594+
auto addEndScopeInst = [&](SILInstruction *endScope) {
595+
workList.push_back({endScope, endScope->getParent()});
596+
bool isNew = visitedBlocks.insert(endScope->getParent());
597+
(void)isNew;
598+
assert(isNew);
599+
};
600+
601+
// First step: add all scope-ending instructions to the worklist.
602+
if (auto *beginAccess = dyn_cast<BeginAccessInst>(beginScopeInst)) {
603+
for (EndAccessInst *endAccess : beginAccess->getEndAccesses()) {
604+
addEndScopeInst(endAccess);
605+
}
606+
} else {
607+
visitTransitiveEndBorrows(BorrowedValue(beginScopeInst), addEndScopeInst);
608+
}
609+
610+
// Second step: walk up the control flow until the beginScopeInst and add
611+
// all (potentially) memory writing instructions to instsInImmutableScopes.
612+
while (!workList.empty()) {
613+
auto instAndBlock = workList.pop_back_val();
614+
SILBasicBlock *block = instAndBlock.second;
615+
// If the worklist entry doesn't have an instruction, start at the end of
616+
// the block.
617+
auto iter = instAndBlock.first ? instAndBlock.first->getIterator()
618+
: block->end();
619+
// Walk up the instruction list - either to the begin of the block or until
620+
// we hit the beginScopeInst.
621+
while (true) {
622+
if (iter == block->begin()) {
623+
assert(block != block->getParent()->getEntryBlock() &&
624+
"didn't find the beginScopeInst when walking up the CFG");
625+
// Add all predecessor blocks to the worklist.
626+
for (SILBasicBlock *pred : block->getPredecessorBlocks()) {
627+
if (visitedBlocks.insert(pred))
628+
workList.push_back({nullptr, pred});
629+
}
630+
break;
631+
}
632+
--iter;
633+
SILInstruction *inst = &*iter;
634+
if (inst == beginScopeInst) {
635+
// When we are at the beginScopeInst we terminate the CFG walk.
636+
break;
637+
}
638+
if (inst->mayWriteToMemory()) {
639+
instsInImmutableScopes.insert({beginIdx,
640+
InstructionToIndex.getIndex(inst)});
641+
}
642+
}
643+
}
644+
}
645+
646+
/// Returns true if \p inst is in an immutable scope of V.
647+
///
648+
/// That means that even if we don't know anything about inst, we can be sure
649+
/// that inst cannot write to V.
650+
/// An immutable scope is for example a read-only begin_access/end_access scope.
651+
/// Another example is a borrow scope of an immutable copy-on-write buffer.
652+
bool AliasAnalysis::isInImmutableScope(SILInstruction *inst, SILValue V) {
653+
if (!V->getType().isAddress())
654+
return false;
655+
656+
SILValue beginScope = getBeginScopeInst(V);
657+
if (!beginScope)
658+
return false;
659+
660+
if (auto *funcArg = dyn_cast<SILFunctionArgument>(beginScope)) {
661+
// The immutable scope (= an guaranteed argument) spans over the whole
662+
// function. We don't need to do any scope computation in this case.
663+
assert(funcArg->getArgumentConvention().isGuaranteedConvention());
664+
return true;
665+
}
666+
667+
auto *beginScopeInst = dyn_cast<SingleValueInstruction>(beginScope);
668+
if (!beginScopeInst)
669+
return false;
670+
671+
ValueIndexTy beginIdx = InstructionToIndex.getIndex(beginScopeInst);
672+
673+
// Recompute the scope if not done yet.
674+
if (immutableScopeComputed.insert(beginIdx).second) {
675+
// In practice this will never happen. Just be be sure to not run into
676+
// quadratic complexity in obscure corner cases.
677+
if (instsInImmutableScopes.size() > ImmutableScopeInstsMaxSize)
678+
return false;
679+
680+
computeImmutableScope(beginScopeInst, beginIdx);
681+
}
682+
683+
ValueIndexTy instIdx = InstructionToIndex.getIndex(inst);
684+
return instsInImmutableScopes.contains({beginIdx, instIdx});
685+
}
686+
539687
MemBehavior
540688
AliasAnalysis::computeMemoryBehaviorInner(SILInstruction *Inst, SILValue V) {
541689
LLVM_DEBUG(llvm::dbgs() << "GET MEMORY BEHAVIOR FOR:\n " << *Inst << " "
542690
<< *V);
543691
assert(SEA && "SideEffectsAnalysis must be initialized!");
544-
return MemoryBehaviorVisitor(this, SEA, EA, V).visit(Inst);
692+
693+
MemBehavior result = MemoryBehaviorVisitor(this, SEA, EA, V).visit(Inst);
694+
695+
// If the "regular" alias analysis thinks that Inst may modify V, check if
696+
// Inst is in an immutable scope of V.
697+
if (result > MemBehavior::MayRead && isInImmutableScope(Inst, V)) {
698+
return (result == MemBehavior::MayWrite) ? MemBehavior::None
699+
: MemBehavior::MayRead;
700+
}
701+
return result;
545702
}
546703

547704
MemBehaviorKeyTy AliasAnalysis::toMemoryBehaviorKey(SILInstruction *V1,
548705
SILValue V2) {
549-
size_t idx1 = InstructionToIndex.getIndex(V1);
550-
assert(idx1 != std::numeric_limits<size_t>::max() &&
706+
ValueIndexTy idx1 = InstructionToIndex.getIndex(V1);
707+
assert(idx1 != std::numeric_limits<ValueIndexTy>::max() &&
551708
"~0 index reserved for empty/tombstone keys");
552-
size_t idx2 = ValueToIndex.getIndex(V2);
553-
assert(idx2 != std::numeric_limits<size_t>::max() &&
709+
ValueIndexTy idx2 = ValueToIndex.getIndex(V2);
710+
assert(idx2 != std::numeric_limits<ValueIndexTy>::max() &&
554711
"~0 index reserved for empty/tombstone keys");
555712
return {idx1, idx2};
556713
}

0 commit comments

Comments
 (0)