Skip to content

Commit 22d411c

Browse files
author
Joe Shajrawi
authored
Merge pull request #20053 from shajrawi/enforce_dom
[Exclusivity] Remove dominated access checks with no nested conflict.
2 parents dda7c10 + b5508f5 commit 22d411c

File tree

9 files changed

+669
-13
lines changed

9 files changed

+669
-13
lines changed

include/swift/SIL/MemAccessUtils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,12 @@ class AccessedStorage {
273273
llvm_unreachable("unhandled kind");
274274
}
275275

276+
bool isUniquelyIdentifiedOrClass() const {
277+
if (isUniquelyIdentified())
278+
return true;
279+
return (getKind() == Class);
280+
}
281+
276282
bool isDistinctFrom(const AccessedStorage &other) const {
277283
if (isUniquelyIdentified() && other.isUniquelyIdentified()) {
278284
return !hasIdenticalBase(other);

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: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,10 @@ bool SILDeclRef::isNoinline() const {
533533
if (auto InlineA = getDecl()->getAttrs().getAttribute<InlineAttr>())
534534
if (InlineA->getKind() == InlineKind::Never)
535535
return true;
536-
return false;
536+
if (auto *semanticsA = getDecl()->getAttrs().getAttribute<SemanticsAttr>())
537+
if (semanticsA->Value.equals("keypath.entry"))
538+
return true;
539+
return false;
537540
}
538541

539542
/// \brief True if the function has noinline attribute.

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: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
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+
#define _MAX_ACCESS_DOM_OPT_RECURSION_DEPTH 100
40+
#define _MAX_ACCESS_DOM_OPT_UNIQUE_STORAGE_LOCS 42
41+
42+
using namespace swift;
43+
44+
namespace {
45+
class DominatedAccessRemoval {
46+
public:
47+
using AccessedStoragePair = std::pair<BeginAccessInst *, AccessedStorage>;
48+
using AccessedStorageInfo = llvm::SmallVector<AccessedStoragePair, 32>;
49+
using DominatedInstVec = llvm::SmallVector<BeginAccessInst *, 32>;
50+
using KeyPathEntryPointsSet = llvm::SmallSet<SILInstruction *, 8>;
51+
52+
public:
53+
DominatedAccessRemoval(SILFunction &func, DominanceInfo *domInfo)
54+
: func(func), domInfo(domInfo) {}
55+
56+
void perform();
57+
58+
protected:
59+
bool visitInstruction(SILInstruction *instr,
60+
AccessedStorageInfo &visitedDomAccessesToStorageInfo);
61+
void visitBeginAccess(BeginAccessInst *beginAccess, AccessedStorage storage,
62+
AccessedStorageInfo &visitedDomAccessesToStorageInfo);
63+
bool analyzeDomSubTree(SILBasicBlock *block,
64+
AccessedStorageInfo &visitedDomAccessesToStorageInfo,
65+
size_t recursionDepth);
66+
67+
bool analyze();
68+
void optimize();
69+
70+
private:
71+
SILFunction &func;
72+
DominanceInfo *domInfo;
73+
// domInstrs is a vector of Dominated begin_access instructions.
74+
// the accesses are dynamic, and the Dominated one has no nested conflict
75+
// we can turn the dominated to static during the optimize() phase
76+
DominatedInstVec domInstrs;
77+
};
78+
} // namespace
79+
80+
// Returns a bool: If we should bail on this function
81+
// we return false - else true
82+
// See the discussion in DominatedAccessRemoval::analyze() below
83+
bool DominatedAccessRemoval::visitInstruction(
84+
SILInstruction *instr,
85+
AccessedStorageInfo &visitedDomAccessesToStorageInfo) {
86+
if (auto *BAI = dyn_cast<BeginAccessInst>(instr)) {
87+
if (BAI->getEnforcement() != SILAccessEnforcement::Dynamic) {
88+
return true;
89+
}
90+
AccessedStorage storage = findAccessedStorageNonNested(BAI->getSource());
91+
if (!storage) {
92+
return true;
93+
}
94+
95+
visitBeginAccess(BAI, storage, visitedDomAccessesToStorageInfo);
96+
} else if (auto fullApply = FullApplySite::isa(instr)) {
97+
SILFunction *callee = fullApply.getReferencedFunction();
98+
if (!callee)
99+
return true;
100+
if (!callee->hasSemanticsAttr("keypath.entry"))
101+
return true;
102+
// we can't eliminate dominated checks even when we can prove that
103+
// the dominated scope has no internal nested conflicts.
104+
// We just bail on these functions.
105+
// Aprevious commit handled them, you can see the full support there,
106+
// but, to simplify the code, assuming key-paths are rare for now, bail.
107+
return false;
108+
} else if (auto *BUAI = dyn_cast<BeginUnpairedAccessInst>(instr)) {
109+
// We have an Implementation that handles this in the analyzer
110+
// and optimizer in a previous commit, However,
111+
// In order to simplify the code, at least in the initial version,
112+
// We decided to just bail on these extremely rare cases
113+
return false;
114+
}
115+
return true;
116+
}
117+
118+
void DominatedAccessRemoval::visitBeginAccess(
119+
BeginAccessInst *beginAccess, AccessedStorage storage,
120+
AccessedStorageInfo &visitedDomAccessesToStorageInfo) {
121+
if (!storage.isUniquelyIdentifiedOrClass()) {
122+
// No cannot do anything about this location -
123+
// bail on trying to turn it to static
124+
return;
125+
}
126+
127+
// checks if the current storage has never been seen before
128+
auto predNewStorageLoc = [&](AccessedStoragePair it) {
129+
auto currStorage = it.second;
130+
return !currStorage.isDistinctFrom(storage);
131+
};
132+
// The size of this list should be tiny: number of storage locations
133+
auto visitedAccessesIt =
134+
std::find_if(visitedDomAccessesToStorageInfo.begin(),
135+
visitedDomAccessesToStorageInfo.end(), predNewStorageLoc);
136+
137+
if (visitedAccessesIt == visitedDomAccessesToStorageInfo.end()) {
138+
// We've never seen this one before - just add it and return
139+
visitedDomAccessesToStorageInfo.push_back(
140+
std::make_pair(beginAccess, storage));
141+
return;
142+
}
143+
144+
// If the currnet access has nested conflict,
145+
// we can't remove it by finding a dominating access
146+
if (!beginAccess->hasNoNestedConflict()) {
147+
return;
148+
}
149+
150+
// check that the storage we found is identical to the
151+
// one we found in order to enable optimization
152+
auto parentStorage = visitedAccessesIt->second;
153+
if (!parentStorage.hasIdenticalBase(storage)) {
154+
return;
155+
}
156+
157+
domInstrs.push_back(beginAccess);
158+
}
159+
160+
bool DominatedAccessRemoval::analyzeDomSubTree(
161+
SILBasicBlock *block, AccessedStorageInfo &visitedDomAccessesToStorageInfo,
162+
size_t recursionDepth) {
163+
if (recursionDepth > _MAX_ACCESS_DOM_OPT_RECURSION_DEPTH) {
164+
return false;
165+
}
166+
// We will modify the incoming visitedDomAccessesToStorageInfo,
167+
// after finishing with the sub-tree,
168+
// we need to restore it to its previous state.
169+
// the state should be small because the number of unique storage
170+
// locations, which is the size of the data structure,
171+
// should be quite small. a handful at most.
172+
auto numOfElems = visitedDomAccessesToStorageInfo.size();
173+
if (numOfElems > _MAX_ACCESS_DOM_OPT_UNIQUE_STORAGE_LOCS) {
174+
return false;
175+
}
176+
177+
// analyze the current block:
178+
for (auto &instr : *block) {
179+
if (!visitInstruction(&instr, visitedDomAccessesToStorageInfo))
180+
return false;
181+
}
182+
// do the same for each child:
183+
auto *domNode = domInfo->getNode(block);
184+
for (auto *child : *domNode) {
185+
if (!analyzeDomSubTree(child->getBlock(), visitedDomAccessesToStorageInfo,
186+
recursionDepth + 1))
187+
return false;
188+
}
189+
190+
// Restore the sets to their previous state as described above,
191+
// removing all "new" elements
192+
assert(visitedDomAccessesToStorageInfo.size() >= numOfElems &&
193+
"Expected the size of visitedStorageLocs to be the same or higher");
194+
auto rmInfoStart = visitedDomAccessesToStorageInfo.begin() + numOfElems;
195+
visitedDomAccessesToStorageInfo.erase(rmInfoStart,
196+
visitedDomAccessesToStorageInfo.end());
197+
assert(visitedDomAccessesToStorageInfo.size() == numOfElems &&
198+
"Number of elems should stay the same");
199+
return true;
200+
}
201+
202+
// Finds domPairs for which we can change the dominated instruction to static
203+
// NOTE: We might not be able to optimize some the pairs due to other
204+
// restrictions Such as key-path or unpaired begin access. We only traverse the
205+
// function once, if we find a pattern that *might* prevent optimization, we
206+
// just add it to appropriate data structures which will be analyzed later.
207+
// If we should bail on this function we return false - else true
208+
// we bail to simplify the code instead of handling unpaired accesses
209+
// in the optimize phase
210+
bool DominatedAccessRemoval::analyze() {
211+
SILBasicBlock *entry = &func.front();
212+
AccessedStorageInfo visitedDomAccessesToStorageInfo;
213+
214+
return analyzeDomSubTree(entry, visitedDomAccessesToStorageInfo,
215+
0 /*recursion-depth*/);
216+
}
217+
218+
// Sets the dominated instruction to static.
219+
// Goes through the data structures initialized by the analysis method
220+
// and makes sure we are not Weakening enforcement
221+
void DominatedAccessRemoval::optimize() {
222+
for (BeginAccessInst *dominatedInstr : domInstrs) {
223+
LLVM_DEBUG(llvm::dbgs() << "Processing optimizable dominated instruction: "
224+
<< dominatedInstr << "\n");
225+
LLVM_DEBUG(llvm::dbgs() << "Setting " << *dominatedInstr
226+
<< " access enforcement to static\n");
227+
dominatedInstr->setEnforcement(SILAccessEnforcement::Static);
228+
}
229+
}
230+
231+
void DominatedAccessRemoval::perform() {
232+
if (func.empty())
233+
return;
234+
235+
if (!analyze()) {
236+
LLVM_DEBUG(llvm::dbgs()
237+
<< "Bailed on function: " << func.getName() << "\n");
238+
return;
239+
}
240+
optimize();
241+
}
242+
243+
namespace {
244+
struct AccessEnforcementDom : public SILFunctionTransform {
245+
void run() override {
246+
DominanceAnalysis *domAnalysis = getAnalysis<DominanceAnalysis>();
247+
DominanceInfo *domInfo = domAnalysis->get(getFunction());
248+
DominatedAccessRemoval eliminationPass(*getFunction(), domInfo);
249+
eliminationPass.perform();
250+
}
251+
};
252+
} // namespace
253+
254+
SILTransform *swift::createAccessEnforcementDom() {
255+
return new AccessEnforcementDom();
256+
}

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

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 @_semantics("keypath.entry") 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+
@_semantics("keypath.entry")
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+
@_semantics("keypath.entry")
16691677
public // COMPILER_INTRINSIC
16701678
func _projectKeyPathReferenceWritable<Root, Value>(
16711679
root: Root,

0 commit comments

Comments
 (0)