Skip to content

Commit 3701de4

Browse files
committed
AliasAnalysis: check for immutable scopes.
If the "regular" alias analysis thinks that an instruction may write to an address, check if the instruction is in an immutable scope of V. That means that even if we don't know anything about the instruction (e.g. a call to an unknown function), we can be sure that it cannot write to the address. 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, for example: %b = begin_borrow %array_buffer %addr = ref_element_addr [immutable] %b : $BufferType, #BufferType.someField
1 parent 4affa11 commit 3701de4

File tree

4 files changed

+315
-10
lines changed

4 files changed

+315
-10
lines changed

include/swift/SILOptimizer/Analysis/AliasAnalysis.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 *,

lib/SILOptimizer/Analysis/MemoryBehavior.cpp

Lines changed: 158 additions & 1 deletion
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,12 +543,162 @@ 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,

test/SILOptimizer/licm_exclusivity.sil

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ sil hidden_external @globalAddressor : $@convention(thin) () -> Builtin.RawPoint
2929
// CHECK-LABEL: sil @hoist_access_with_conflict : $@convention(thin) () -> () {
3030
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
3131
// CHECK: [[BEGIN:%.*]] = begin_access [read] [dynamic] [[GLOBAL]] : $*X
32+
// CHECK-NEXT: load
3233
// CHECK-NEXT: br bb1
3334
// CHECK: apply
34-
// CHECK: load
35-
// CHECK: cond_br
35+
// CHECK-NEXT: cond_br
3636
// CHECK: bb2
3737
// CHECK: end_access [[BEGIN]]
3838
// CHECK-LABEL: } // end sil function 'hoist_access_with_conflict'
@@ -89,9 +89,9 @@ bb2:
8989
// CHECK-LABEL: sil @hoist_access_with_may_release : $@convention(thin) () -> () {
9090
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
9191
// CHECK: [[BEGIN:%.*]] = begin_access [read] [dynamic] [[GLOBAL]] : $*X
92+
// CHECK-NEXT: load
9293
// CHECK-NEXT: br bb1
9394
// CHECK: apply
94-
// CHECK: load
9595
// CHECK: cond_br
9696
// CHECK: bb2
9797
// CHECK: end_access [[BEGIN]]
@@ -153,10 +153,10 @@ bb2:
153153
// CHECK-LABEL: sil @hoist_access_static : $@convention(thin) () -> () {
154154
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
155155
// CHECK: [[BEGIN:%.*]] = begin_access [read] [static] [[GLOBAL]] : $*X
156+
// CHECK-NEXT: load
156157
// CHECK-NEXT: br bb1
157158
// CHECK: apply
158-
// CHECK: load
159-
// CHECK: cond_br
159+
// CHECK-NEXT: cond_br
160160
// CHECK: bb2
161161
// CHECK: end_access [[BEGIN]]
162162
// CHECK-LABEL: } // end sil function 'hoist_access_static'

0 commit comments

Comments
 (0)