Skip to content

Commit a9070cf

Browse files
committed
Add AccessEnforcementOpts fast paths.
1. During identifyAccess, determine if there are either any identical accesses or an accesses that aren't already marked no_nested_storage. If there are neither, then skip the subsequent data flow analysis. 2. In the new StorageSet, indicate whether identical storage was seen elsewhere in the function. During dataflow, only add an access to the out-of-scope access set if was marked as having identical storage with another access. 3. During data flow, don't track in scope conflicts for instructions already marked [no_nested_conflict].
1 parent c08e439 commit a9070cf

File tree

3 files changed

+196
-23
lines changed

3 files changed

+196
-23
lines changed

include/swift/SIL/MemAccessUtils.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ class AccessedStorage {
143143
SWIFT_INLINE_BITFIELD_FULL(AccessEnforcementOptsInfo, AccessedStorage,
144144
64 - NumAccessedStorageBits,
145145
seenNestedConflict : 1,
146-
beginAccessIndex : 63 - NumAccessedStorageBits);
146+
seenIdenticalStorage : 1,
147+
beginAccessIndex : 62 - NumAccessedStorageBits);
147148

148149
// Define data flow bits for use in the AccessEnforcementDom pass. Each
149150
// begin_access in the function is mapped to one instance of this subclass.

lib/SILOptimizer/Transforms/AccessEnforcementOpts.cpp

Lines changed: 83 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,23 @@ class AccessEnforcementOptsInfo : public AccessedStorage {
129129
Bits.AccessEnforcementOptsInfo.seenNestedConflict = 1;
130130
}
131131

132+
/// Did a PostOrder walk previously find another access to the same
133+
/// storage. If so, then this access could be merged with a subsequent access
134+
/// after checking for conflicts.
135+
bool seenIdenticalStorage() const {
136+
return Bits.AccessEnforcementOptsInfo.seenIdenticalStorage;
137+
}
138+
139+
void setSeenIdenticalStorage() {
140+
Bits.AccessEnforcementOptsInfo.seenIdenticalStorage = 1;
141+
}
142+
132143
void dump() const {
133144
AccessedStorage::dump();
134145
llvm::dbgs() << " access index: " << getAccessIndex() << " <"
135-
<< (seenNestedConflict() ? "" : "no ") << "conflict>\n";
146+
<< (seenNestedConflict() ? "" : "no ") << "conflict> <"
147+
<< (seenIdenticalStorage() ? "" : "not ") << "seen identical>"
148+
<< "\n";
136149
}
137150
};
138151
using AccessInfo = AccessEnforcementOptsInfo;
@@ -267,21 +280,26 @@ class AccessConflictAndMergeAnalysis {
267280

268281
private:
269282
LoopRegionFunctionInfo *LRFI;
283+
PostOrderFunctionInfo *PO;
270284
AccessedStorageAnalysis *ASA;
271285

286+
// Unique storage locations seen in this function.
287+
AccessedStorageSet storageSet;
288+
272289
Result result;
273290

274291
public:
275292
AccessConflictAndMergeAnalysis(LoopRegionFunctionInfo *LRFI,
293+
PostOrderFunctionInfo *PO,
276294
AccessedStorageAnalysis *ASA)
277-
: LRFI(LRFI), ASA(ASA) {}
295+
: LRFI(LRFI), PO(PO), ASA(ASA) {}
278296

279-
void analyze();
297+
bool analyze();
280298

281299
const Result &getResult() { return result; }
282300

283301
protected:
284-
void identifyBeginAccesses();
302+
bool identifyBeginAccesses();
285303

286304
void
287305
propagateAccessSetsBottomUp(LoopRegionToAccessedStorage &regionToStorageMap,
@@ -456,6 +474,11 @@ void AccessConflictAndMergeAnalysis::insertOutOfScopeAccess(
456474
RegionState &state, BeginAccessInst *beginAccess,
457475
AccessInfo &currStorageInfo) {
458476

477+
if (!currStorageInfo.seenIdenticalStorage()) {
478+
LLVM_DEBUG(llvm::dbgs() << "Ignoring unmergeable access: " << *beginAccess);
479+
return;
480+
}
481+
459482
auto identicalStorageIter = llvm::find_if(
460483
state.outOfScopeConflictFreeAccesses, [&](BeginAccessInst *bai) {
461484
auto storageInfo = result.getAccessInfo(bai);
@@ -470,8 +493,13 @@ void AccessConflictAndMergeAnalysis::insertOutOfScopeAccess(
470493
}
471494

472495
// Top-level driver for AccessConflictAndMergeAnalysis
473-
void AccessConflictAndMergeAnalysis::analyze() {
474-
identifyBeginAccesses();
496+
//
497+
// Returns true if the analysis succeeded.
498+
bool AccessConflictAndMergeAnalysis::analyze() {
499+
if (!identifyBeginAccesses()) {
500+
LLVM_DEBUG(llvm::dbgs() << "Skipping AccessConflictAndMergeAnalysis...\n");
501+
return false;
502+
}
475503
LoopRegionToAccessedStorage accessSetsOfRegions;
476504
// Populate a worklist of regions such that the top of the worklist is the
477505
// innermost loop and the bottom of the worklist is the entry block.
@@ -501,6 +529,7 @@ void AccessConflictAndMergeAnalysis::analyze() {
501529
}
502530
}
503531
}
532+
return true;
504533
}
505534

506535
// Find all begin access operations in this function. Map each access to
@@ -509,29 +538,52 @@ void AccessConflictAndMergeAnalysis::analyze() {
509538
//
510539
// Also, add the storage location to the function's RegionStorage
511540
//
541+
// Returns true if it is worthwhile to continue the analysis.
542+
//
512543
// TODO: begin_unpaired_access is not tracked. Even though begin_unpaired_access
513544
// isn't explicitly paired, it may be possible after devirtualization and
514545
// inlining to find all uses of the scratch buffer. However, this doesn't
515546
// currently happen in practice (rdar://40033735).
516-
void AccessConflictAndMergeAnalysis::identifyBeginAccesses() {
517-
for (auto &BB : *LRFI->getFunction()) {
518-
for (auto &I : BB) {
547+
bool AccessConflictAndMergeAnalysis::identifyBeginAccesses() {
548+
bool seenPossibleNestedConflict = false;
549+
bool seenIdenticalStorage = false;
550+
// Scan blocks in PostOrder (bottom-up) to mark any accesses with identical
551+
// storage to another reachable access. The earlier access must be marked
552+
// because this analysis does forward data flow to find conflicts.
553+
for (auto *BB : PO->getPostOrder()) {
554+
for (auto &I : llvm::reverse(*BB)) {
519555
auto *beginAccess = dyn_cast<BeginAccessInst>(&I);
520556
if (!beginAccess)
521557
continue;
522558

523559
if (beginAccess->getEnforcement() != SILAccessEnforcement::Dynamic)
524560
continue;
525561

562+
if (!beginAccess->hasNoNestedConflict())
563+
seenPossibleNestedConflict = true;
564+
526565
// The accessed base is expected to be valid for begin_access, but for
527566
// now, since this optimization runs at the end of the pipeline, we
528567
// gracefully ignore unrecognized source address patterns, which show up
529568
// here as an invalid `storage` value.
530-
const AccessedStorage &storage =
569+
AccessedStorage storage =
531570
findAccessedStorageNonNested(beginAccess->getSource());
532571

533-
auto iterAndSuccess = result.accessMap.try_emplace(
534-
beginAccess, static_cast<const AccessInfo &>(storage));
572+
auto iterAndInserted = storageSet.insert(storage);
573+
574+
// After inserting it in storageSet, this storage object can be downcast
575+
// to AccessInfo to use the pass-specific bits.
576+
auto &accessInfo = static_cast<AccessInfo &>(storage);
577+
578+
// If the same location was seen later in the CFG, mark this access as one
579+
// to check for merging.
580+
if (!iterAndInserted.second) {
581+
seenIdenticalStorage = true;
582+
accessInfo.setSeenIdenticalStorage();
583+
}
584+
585+
auto iterAndSuccess =
586+
result.accessMap.try_emplace(beginAccess, accessInfo);
535587
(void)iterAndSuccess;
536588
assert(iterAndSuccess.second);
537589

@@ -541,6 +593,7 @@ void AccessConflictAndMergeAnalysis::identifyBeginAccesses() {
541593
assert(!info.seenNestedConflict());
542594
}
543595
}
596+
return seenPossibleNestedConflict || seenIdenticalStorage;
544597
}
545598

546599
// Returns a mapping from each loop sub-region to all its access storage
@@ -619,11 +672,14 @@ void AccessConflictAndMergeAnalysis::visitBeginAccess(
619672
recordInScopeConflicts(state, beginAccessInfo, beginAccess->getAccessKind());
620673
// Remove in-scope conflicts to avoid checking them again.
621674
removeConflicts(state.inScopeConflictFreeAccesses, beginAccessInfo);
622-
// Always record the current access as in-scope. It can potentially be folded
623-
// to [no_nested_conflict] independent of any enclosing access conflicts.
624-
bool inserted = state.inScopeConflictFreeAccesses.insert(beginAccess);
625-
(void)inserted;
626-
assert(inserted && "the begin_access should not have been seen yet.");
675+
676+
if (!beginAccess->hasNoNestedConflict()) {
677+
// Record the current access as in-scope. It can potentially be folded to
678+
// [no_nested_conflict] independent of any enclosing access conflicts.
679+
bool inserted = state.inScopeConflictFreeAccesses.insert(beginAccess);
680+
(void)inserted;
681+
assert(inserted && "the begin_access should not have been seen yet.");
682+
}
627683

628684
// Find an out-of-scope access that is mergeable with this access. This is
629685
// done at the BeginAccess because it doesn't matter whether the merged access
@@ -637,7 +693,7 @@ void AccessConflictAndMergeAnalysis::visitBeginAccess(
637693
if (BeginAccessInst *mergeableAccess =
638694
findMergeableOutOfScopeAccess(state, beginAccess)) {
639695
LLVM_DEBUG(llvm::dbgs() << "Found mergable pair: " << *mergeableAccess
640-
<< ", " << *beginAccess << "\n");
696+
<< " with " << *beginAccess << "\n");
641697
result.mergePairs.emplace_back(mergeableAccess, beginAccess);
642698
}
643699
// For the purpose of data-flow, removing the out-of-scope access does not
@@ -950,8 +1006,10 @@ static bool mergeAccesses(
9501006
SILFunction *F, PostDominanceInfo *postDomTree,
9511007
const AccessConflictAndMergeAnalysis::MergeablePairs &mergePairs) {
9521008

953-
if (mergePairs.empty())
1009+
if (mergePairs.empty()) {
1010+
LLVM_DEBUG(llvm::dbgs() << "Skipping SCC Analysis...\n");
9541011
return false;
1012+
}
9551013

9561014
bool changed = false;
9571015

@@ -999,7 +1057,7 @@ static bool mergeAccesses(
9991057
continue;
10001058

10011059
LLVM_DEBUG(llvm::dbgs()
1002-
<< "Merging: " << *childIns << " into " << *parentIns << "\n");
1060+
<< "Merging " << *childIns << " into " << *parentIns << "\n");
10031061

10041062
// Change the no nested conflict of parent if the child has a nested
10051063
// conflict.
@@ -1044,9 +1102,12 @@ struct AccessEnforcementOpts : public SILFunctionTransform {
10441102
<< F->getName() << "\n");
10451103

10461104
LoopRegionFunctionInfo *LRFI = getAnalysis<LoopRegionAnalysis>()->get(F);
1105+
PostOrderFunctionInfo *PO = getAnalysis<PostOrderAnalysis>()->get(F);
10471106
AccessedStorageAnalysis *ASA = getAnalysis<AccessedStorageAnalysis>();
1048-
AccessConflictAndMergeAnalysis a(LRFI, ASA);
1049-
a.analyze();
1107+
AccessConflictAndMergeAnalysis a(LRFI, PO, ASA);
1108+
if (!a.analyze())
1109+
return;
1110+
10501111
auto result = a.getResult();
10511112

10521113
// Perform access folding by setting the [no_nested_conflict] flag on
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// RUN: %target-sil-opt -access-enforcement-opts -debug-only=access-enforcement-opts -verify %s -o /dev/null 2>&1 | %FileCheck %s
2+
//
3+
// REQUIRES: asserts
4+
//
5+
// This tests four kinds of AccessEnforcementOpts fast paths:
6+
//
7+
// 1. During identifyAccess, determine if there are either any
8+
// identical accesses or an accesses that aren't already marked
9+
// no_nested_storage. If there are neither, then skip the subsequent
10+
// data flow analysis.
11+
//
12+
// 2. In StorageSet, indicate whether identical storage was seen
13+
// elsewhere in the function. During dataflow, only add an access to
14+
// the out-of-scope access set if was marked as having identical
15+
// storage with another access.
16+
//
17+
// 3. If no access pairs can be merged, avoid analyzing SCC regions in
18+
// the control flow graph.
19+
//
20+
// 4. During data flow, don't track in scope conflicts for
21+
// instructions already marked [no_nested_conflict].
22+
23+
sil_stage canonical
24+
25+
import Builtin
26+
import Swift
27+
import SwiftShims
28+
29+
public class TestEarlyBail {
30+
@_hasStorage var prop1: Int { get set }
31+
@_hasStorage var prop2: Int { get set }
32+
}
33+
34+
// All accesses are no_nested_conflict, and none are identical. Bail.
35+
// (1) Skipping AccessConflictAndMergeAnalysis...
36+
//
37+
// CHECK-LABEL: Running local AccessEnforcementOpts on testEarlyBail
38+
// CHECK-NOT: Processing
39+
// CHECK-NOT: No conflict on one path
40+
// CHECK-NOT: Folding
41+
// CHECK-NOT: Merging
42+
// CHECK: Skipping AccessConflictAndMergeAnalysis...
43+
sil @testEarlyBail : $@convention(method) (@owned TestEarlyBail) -> @owned TestEarlyBail {
44+
bb0(%0 : $TestEarlyBail):
45+
%adr1 = ref_element_addr %0 : $TestEarlyBail, #TestEarlyBail.prop1
46+
%a1 = begin_access [modify] [dynamic] [no_nested_conflict] %adr1 : $*Int
47+
%v1 = load %a1 : $*Int
48+
end_access %a1 : $*Int
49+
%adr2 = ref_element_addr %0 : $TestEarlyBail, #TestEarlyBail.prop2
50+
%a2 = begin_access [modify] [dynamic] [no_nested_conflict] %adr2 : $*Int
51+
%v2 = load %a2 : $*Int
52+
end_access %a2 : $*Int
53+
return %0 : $TestEarlyBail
54+
}
55+
56+
// An access is not marked no_nested_conflict. Need to run analysis.
57+
// NOT (1) Skipping AccessConflictAndMergeAnalysis...
58+
// (2) Ignoring unique access...
59+
// (3) Skipping SCC analysis...
60+
//
61+
// CHECK-LABEL: Running local AccessEnforcementOpts on testEarlyBailNeedsFolding
62+
// CHECK: Processing Function: testEarlyBailNeedsFolding
63+
// CHECK: No conflict on one path from [[ACCESS:%.*]] = begin_access [modify] [dynamic] %{{.*}} : $*Int
64+
// CHECK-NEXT: to end_access [[ACCESS]] : $*Int
65+
// CHECK: Ignoring unmergeable access: [[ACCESS]] = begin_access [modify] [dynamic] %{{.*}} : $*Int
66+
// CHECK: Folding [[ACCESS]] = begin_access [modify] [dynamic] [no_nested_conflict] %{{.*}} : $*Int
67+
// CHECK: Skipping SCC Analysis...
68+
sil @testEarlyBailNeedsFolding : $@convention(method) (@owned TestEarlyBail) -> @owned TestEarlyBail {
69+
bb0(%0 : $TestEarlyBail):
70+
%adr1 = ref_element_addr %0 : $TestEarlyBail, #TestEarlyBail.prop1
71+
%a1 = begin_access [modify] [dynamic] %adr1 : $*Int
72+
%v1 = load %a1 : $*Int
73+
end_access %a1 : $*Int
74+
return %0 : $TestEarlyBail
75+
}
76+
77+
// Two accesses have the same storage. Need to run analysis.
78+
// NOT (1) Skipping AccessConflictAndMergeAnalysis...
79+
// NOT (2) Ignoring unique access...
80+
// (4) No Conflict On Path
81+
// (4) NOT No Conflict On Path
82+
// NOT (3) Skipping SCC analysis...
83+
//
84+
// CHECK-LABEL: Running local AccessEnforcementOpts on testEarlyBailMayMerge
85+
// CHECK: Processing Function: testEarlyBailMayMerge
86+
// CHECK: No conflict on one path from [[ACCESS1:%.*]] = begin_access [modify] [dynamic] %{{.*}} : $*Int
87+
// CHECK: to end_access [[ACCESS1]] : $*Int
88+
// CHECK: Found mergable pair: [[ACCESS1]] = begin_access [modify] [dynamic] %{{.*}} : $*Int
89+
// CHECK-NEXT: with [[ACCESS2:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] %{{.*}} : $*Int
90+
// CHECK-NOT: No conflict on one path from [[ACCESS2]]
91+
// CHECK: Ignoring unmergeable access: [[ACCESS2]] = begin_access [modify] [dynamic] [no_nested_conflict] %{{.*}} : $*Int
92+
// CHECK-NOT: No conflict on one path from [[ACCESS2]]
93+
//
94+
// Note: The order that accesses with no nested conflicts are "Folded" is nondeterministic.
95+
// CHECK-DAG: Folding [[ACCESS1]] = begin_access [modify] [dynamic] [no_nested_conflict] %{{.*}} : $*Int
96+
// CHECK-DAG: Folding [[ACCESS2]] = begin_access [modify] [dynamic] [no_nested_conflict] %{{.*}} : $*Int
97+
// CHECK: Merging [[ACCESS2]] = begin_access [modify] [dynamic] [no_nested_conflict] %{{.*}} : $*Int
98+
// CHECK-NEXT: into [[ACCESS1]] = begin_access [modify] [dynamic] [no_nested_conflict] %{{.*}} : $*Int
99+
// CHECK-NOT: Skipping SCC Analysis...
100+
sil @testEarlyBailMayMerge : $@convention(method) (@owned TestEarlyBail) -> @owned TestEarlyBail {
101+
bb0(%0 : $TestEarlyBail):
102+
%adr1 = ref_element_addr %0 : $TestEarlyBail, #TestEarlyBail.prop1
103+
%a1 = begin_access [modify] [dynamic] %adr1 : $*Int
104+
%v1 = load %a1 : $*Int
105+
end_access %a1 : $*Int
106+
%adr2 = ref_element_addr %0 : $TestEarlyBail, #TestEarlyBail.prop1
107+
%a2 = begin_access [modify] [dynamic] [no_nested_conflict] %adr2 : $*Int
108+
%v2 = load %a2 : $*Int
109+
end_access %a2 : $*Int
110+
return %0 : $TestEarlyBail
111+
}

0 commit comments

Comments
 (0)