Skip to content

Commit 63b50f6

Browse files
author
Joe Shajrawi
committed
[Exclusivity] Remove dominated access checks with no nested conflict.
General case: — begin_access A (may or may not have no_nested_conflict) load/store end_access apply // may have a scoped access that conflicts with A begin_access A [no_nested_conflict] load/store end_access A — The second access scope does not need to be emitted. NOTE: KeyPath access must be identified at the top-level, non-inlinable stdlib entry point. As such, The sodlib entry pointed is annotated by a new @_semantics that is equivalent to inline(never)
1 parent b0389af commit 63b50f6

File tree

11 files changed

+608
-12
lines changed

11 files changed

+608
-12
lines changed

include/swift/AST/Attr.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,10 @@ SIMPLE_DECL_ATTR(_nonoverride, NonOverride,
379379
OnFunc | OnAccessor | OnVar | OnSubscript | OnConstructor | OnAssociatedType |
380380
UserInaccessible | NotSerialized,
381381
79)
382+
SIMPLE_DECL_ATTR(_keyPathEntryPoint, KeyPathEntryPoint,
383+
OnFunc |
384+
UserInaccessible,
385+
80)
382386

383387
#undef TYPE_ATTR
384388
#undef DECL_ATTR_ALIAS

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ PASS(AADumper, "aa-dump",
5454
"Dump Alias Analysis over all Pairs")
5555
PASS(ABCOpt, "abcopts",
5656
"Array Bounds Check Optimization")
57+
PASS(AccessEnforcementDom, "access-enforcement-dom",
58+
"Remove dominated access checks with no nested conflict")
5759
PASS(AccessEnforcementOpts, "access-enforcement-opts",
5860
"Access Enforcement Optimization")
5961
PASS(AccessEnforcementSelection, "access-enforcement-selection",

lib/SIL/SILDeclRef.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,8 @@ IsSerialized_t SILDeclRef::isSerialized() const {
530530
bool SILDeclRef::isNoinline() const {
531531
if (!hasDecl())
532532
return false;
533+
if (getDecl()->getAttrs().hasAttribute<KeyPathEntryPointAttr>())
534+
return true;
533535
if (auto InlineA = getDecl()->getAttrs().getAttribute<InlineAttr>())
534536
if (InlineA->getKind() == InlineKind::Never)
535537
return true;

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,10 @@ static void addLastChanceOptPassPipeline(SILPassPipelinePlan &P) {
496496
// Optimize access markers for improved IRGen after all other optimizations.
497497
P.addAccessEnforcementOpts();
498498
P.addAccessEnforcementWMO();
499+
P.addAccessEnforcementDom();
500+
// addAccessEnforcementDom might provide potential for LICM:
501+
// A loop might have only one dynamic access now, i.e. hoistable
502+
P.addLICM();
499503

500504
// Only has an effect if the -assume-single-thread option is specified.
501505
P.addAssumeSingleThreaded();
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
//===--- AccessEnforcementDom.cpp - dominated access removal opt ---===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
///
13+
/// This function pass removes dynamic access enforcement based on dominance.
14+
///
15+
/// General case:
16+
/// begin_access A (may or may not have no_nested_conflict)
17+
/// load/store
18+
/// end_access
19+
/// ...
20+
/// begin_access A [no_nested_conflict] // dominated by the first access
21+
/// load/store
22+
/// end_access A
23+
/// The second access scope does not need to be emitted.
24+
///
25+
/// Note: This optimization must be aware of all possible access to a Class or
26+
/// Global address. This includes unpaired access instructions and keypath
27+
/// entry points. Ignoring any access pattern would weaken enforcement.
28+
//===----------------------------------------------------------------------===//
29+
30+
#define DEBUG_TYPE "access-enforcement-dom"
31+
32+
#include "swift/SIL/DebugUtils.h"
33+
#include "swift/SIL/MemAccessUtils.h"
34+
#include "swift/SIL/SILFunction.h"
35+
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
36+
#include "swift/SILOptimizer/PassManager/Transforms.h"
37+
#include "swift/SILOptimizer/Utils/Local.h"
38+
39+
using namespace swift;
40+
41+
namespace {
42+
class DominatedAccessRemoval {
43+
public:
44+
using AccessedStoragePair = std::pair<BeginAccessInst *, AccessedStorage>;
45+
using AccessedStorageInfo = llvm::SmallVector<AccessedStoragePair, 32>;
46+
using DominatorToDominatedPair =
47+
std::pair<BeginAccessInst *, BeginAccessInst *>;
48+
using DomPairSet = llvm::SmallVector<DominatorToDominatedPair, 32>;
49+
using KeyPathEntryPointsSet = llvm::SmallSet<SILInstruction *, 8>;
50+
using UnpairedAccessToStoragePair =
51+
std::pair<BeginUnpairedAccessInst *, AccessedStorage>;
52+
using UnpairedAccessToStorageInfo =
53+
llvm::SmallVector<UnpairedAccessToStoragePair, 8>;
54+
55+
public:
56+
DominatedAccessRemoval(SILFunction &func, DominanceInfo *domInfo)
57+
: func(func), domInfo(domInfo) {}
58+
59+
void perform();
60+
61+
protected:
62+
void visitInstruction(SILInstruction *instr);
63+
void visitBeginAccess(BeginAccessInst *beginAccess, AccessedStorage storage);
64+
bool domByKeyPath(BeginAccessInst *dominatedInstr);
65+
bool domByRelevantUnpairedAccess(DominatorToDominatedPair pair);
66+
void analyze();
67+
void optimize();
68+
69+
private:
70+
SILFunction &func;
71+
DominanceInfo *domInfo;
72+
AccessedStorageInfo accessInfo;
73+
DomPairSet domPairs;
74+
KeyPathEntryPointsSet keypathEntries;
75+
UnpairedAccessToStorageInfo unpairedEntries;
76+
};
77+
} // namespace
78+
79+
bool DominatedAccessRemoval::domByKeyPath(BeginAccessInst *dominatedInstr) {
80+
for (SILInstruction *keyPathEntry : keypathEntries) {
81+
if (domInfo->properlyDominates(keyPathEntry, dominatedInstr)) {
82+
return true;
83+
}
84+
}
85+
return false;
86+
}
87+
88+
bool DominatedAccessRemoval::domByRelevantUnpairedAccess(
89+
DominatorToDominatedPair pair) {
90+
BeginAccessInst *parentBegin = pair.first;
91+
BeginAccessInst *dominatedInstr = pair.second;
92+
auto predEqual = [&](AccessedStoragePair it) {
93+
auto currInstr = it.first;
94+
return currInstr == parentBegin;
95+
};
96+
auto currStorageIt =
97+
std::find_if(accessInfo.begin(), accessInfo.end(), predEqual);
98+
assert(currStorageIt != accessInfo.end() && "Expected storage in accessInfo");
99+
AccessedStorage currStorage = currStorageIt->second;
100+
for (UnpairedAccessToStoragePair unpairedEntry : unpairedEntries) {
101+
auto *instr = unpairedEntry.first;
102+
if (!domInfo->properlyDominates(instr, dominatedInstr)) {
103+
continue;
104+
}
105+
auto entryStorage = unpairedEntry.second;
106+
if (!currStorage.isDistinctFrom(entryStorage)) {
107+
return true;
108+
}
109+
}
110+
return false;
111+
}
112+
113+
void DominatedAccessRemoval::visitInstruction(SILInstruction *instr) {
114+
if (auto *BAI = dyn_cast<BeginAccessInst>(instr)) {
115+
if (BAI->getEnforcement() != SILAccessEnforcement::Dynamic) {
116+
return;
117+
}
118+
AccessedStorage storage = findAccessedStorageNonNested(BAI->getSource());
119+
if (!storage) {
120+
return;
121+
}
122+
visitBeginAccess(BAI, storage);
123+
} else if (auto fullApply = FullApplySite::isa(instr)) {
124+
SILFunction *callee = fullApply.getReferencedFunction();
125+
if (!callee)
126+
return;
127+
if (!callee->hasSemanticsAttr("_keyPathEntryPoint"))
128+
return;
129+
// we can't eliminate dominated checks even when we can prove that
130+
// the dominated scope has no internal nested conflicts.
131+
keypathEntries.insert(fullApply.getInstruction());
132+
} else if (auto *BUAI = dyn_cast<BeginUnpairedAccessInst>(instr)) {
133+
AccessedStorage storage = findAccessedStorageNonNested(BUAI->getSource());
134+
unpairedEntries.push_back(std::make_pair(BUAI, storage));
135+
}
136+
}
137+
138+
void DominatedAccessRemoval::visitBeginAccess(BeginAccessInst *beginAccess,
139+
AccessedStorage storage) {
140+
auto predEqual = [&](AccessedStoragePair it) {
141+
auto currStorage = it.second;
142+
return currStorage.hasIdenticalBase(storage);
143+
};
144+
145+
// If the currnet access has nested conflict, just add it to map
146+
// we can't remove it by finding a dominating access
147+
if (!beginAccess->hasNoNestedConflict()) {
148+
accessInfo.push_back(std::make_pair(beginAccess, storage));
149+
return;
150+
}
151+
152+
auto it = std::find_if(accessInfo.begin(), accessInfo.end(), predEqual);
153+
while (it != accessInfo.end()) {
154+
BeginAccessInst *parentBeginAccess = it->first;
155+
if (!domInfo->properlyDominates(parentBeginAccess, beginAccess)) {
156+
++it;
157+
it = std::find_if(it, accessInfo.end(), predEqual);
158+
continue;
159+
}
160+
// Found a pair that can potentially be optimized
161+
domPairs.push_back(std::make_pair(parentBeginAccess, beginAccess));
162+
return;
163+
}
164+
165+
// Did not find a dominating access to same storage
166+
accessInfo.push_back(std::make_pair(beginAccess, storage));
167+
}
168+
169+
// Finds domPairs for which we can change the dominated instruction to static
170+
// NOTE: We might not be able to optimize some the pairs due to other
171+
// restrictions Such as key-path or unpaired begin access We only traverse the
172+
// function once, if we find a pattern that *might* prevent optimization, we
173+
// just add it to appropriate data structures which will be analyzed later.
174+
void DominatedAccessRemoval::analyze() {
175+
SILBasicBlock *entry = &func.front();
176+
DominanceOrder domOrder(entry, domInfo, func.size());
177+
while (SILBasicBlock *block = domOrder.getNext()) {
178+
for (auto &instr : *block) {
179+
visitInstruction(&instr);
180+
}
181+
domOrder.pushChildren(block);
182+
}
183+
}
184+
185+
// Sets the dominated instruction to static.
186+
// Goes through the data structures initialized by the analysis method
187+
// and makes sure we are not Weakening enforcement
188+
void DominatedAccessRemoval::optimize() {
189+
for (DominatorToDominatedPair pair : domPairs) {
190+
LLVM_DEBUG(llvm::dbgs()
191+
<< "Processing optimizable pair - Dominator: " << *pair.first
192+
<< " , Dominated: " << *pair.second << "\n");
193+
BeginAccessInst *dominatedInstr = pair.second;
194+
// look through keypathEntries to see if dominatedInstr
195+
// can no longer be optimized
196+
if (domByKeyPath(dominatedInstr)) {
197+
LLVM_DEBUG(llvm::dbgs()
198+
<< "Can not set " << *dominatedInstr
199+
<< " access enforcement to static - it is properly dominated "
200+
"by a key-path entry point\n");
201+
continue;
202+
}
203+
if (domByRelevantUnpairedAccess(pair)) {
204+
LLVM_DEBUG(llvm::dbgs()
205+
<< "Can not set " << *dominatedInstr
206+
<< " access enforcement to static - there's an unpaired "
207+
"access that is not distinct from it in the way\n");
208+
continue;
209+
}
210+
LLVM_DEBUG(llvm::dbgs() << "Setting " << *dominatedInstr
211+
<< " access enforcement to static\n");
212+
dominatedInstr->setEnforcement(SILAccessEnforcement::Static);
213+
}
214+
}
215+
216+
void DominatedAccessRemoval::perform() {
217+
if (func.empty())
218+
return;
219+
220+
analyze();
221+
optimize();
222+
}
223+
224+
namespace {
225+
struct AccessEnforcementDom : public SILFunctionTransform {
226+
void run() override {
227+
DominanceAnalysis *domAnalysis = getAnalysis<DominanceAnalysis>();
228+
DominanceInfo *domInfo = domAnalysis->get(getFunction());
229+
DominatedAccessRemoval eliminationPass(*getFunction(), domInfo);
230+
eliminationPass.perform();
231+
}
232+
};
233+
} // namespace
234+
235+
SILTransform *swift::createAccessEnforcementDom() {
236+
return new AccessEnforcementDom();
237+
}

lib/SILOptimizer/Transforms/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
silopt_register_sources(
22
ARCCodeMotion.cpp
3+
AccessEnforcementDom.cpp
34
AccessEnforcementOpts.cpp
45
AccessEnforcementWMO.cpp
56
AllocBoxToStack.cpp

lib/Sema/TypeCheckAttr.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ class AttributeEarlyChecker : public AttributeVisitor<AttributeEarlyChecker> {
117117
IGNORED_ATTR(UnsafeNoObjCTaggedPointer)
118118
IGNORED_ATTR(UsableFromInline)
119119
IGNORED_ATTR(WeakLinked)
120+
IGNORED_ATTR(KeyPathEntryPoint)
120121
#undef IGNORED_ATTR
121122

122123
// @noreturn has been replaced with a 'Never' return type.
@@ -818,6 +819,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
818819
IGNORED_ATTR(Transparent)
819820
IGNORED_ATTR(WarnUnqualifiedAccess)
820821
IGNORED_ATTR(WeakLinked)
822+
IGNORED_ATTR(KeyPathEntryPoint)
821823
#undef IGNORED_ATTR
822824

823825
void visitAvailableAttr(AvailableAttr *attr);

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,7 @@ namespace {
12361236
UNINTERESTING_ATTR(WeakLinked)
12371237
UNINTERESTING_ATTR(Frozen)
12381238
UNINTERESTING_ATTR(HasInitialValue)
1239+
UNINTERESTING_ATTR(KeyPathEntryPoint)
12391240
#undef UNINTERESTING_ATTR
12401241

12411242
void visitAvailableAttr(AvailableAttr *attr) {

stdlib/public/core/KeyPath.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1656,7 +1656,15 @@ func _projectKeyPathReadOnly<Root, Value>(
16561656
return keyPath._projectReadOnly(from: root)
16571657
}
16581658

1659-
@inlinable
1659+
// The compiler can't tell which calls might begin an access.
1660+
// That means it can't eliminate dominated checks even when it can prove
1661+
// that the dominated scope has no internal nested conflicts.
1662+
// We use the @_keyPathEntryPoint annotation:
1663+
// This doesn't solve the deinit ending a scope problem,
1664+
// but it solves the much more important half of the problem:
1665+
// identifying the beginning of an access scope -
1666+
// would allow dominance based optimization:
1667+
@_keyPathEntryPoint
16601668
public // COMPILER_INTRINSIC
16611669
func _projectKeyPathWritable<Root, Value>(
16621670
root: UnsafeMutablePointer<Root>,
@@ -1665,7 +1673,7 @@ func _projectKeyPathWritable<Root, Value>(
16651673
return keyPath._projectMutableAddress(from: root)
16661674
}
16671675

1668-
@inlinable
1676+
@_keyPathEntryPoint
16691677
public // COMPILER_INTRINSIC
16701678
func _projectKeyPathReferenceWritable<Root, Value>(
16711679
root: Root,

0 commit comments

Comments
 (0)