Skip to content

Commit 5e7ea88

Browse files
author
Joe Shajrawi
committed
[Exclusivity] Teach MemAccessUtils about Projection Paths
Consider a class ‘C’ with distinct fields ‘A’ and ‘B’ And consider we are accessing C.A and C.B inside a loop LICM well not hoist the exclusivity checking outside of the loop because isDistinctFrom(C.A, C.B) returns false. This is because the helper function bails if isUniquelyIdentified returns false (which is the case in class kinds) Same with all other potential access enforcement optimizations. This PR resolves that
1 parent 643154f commit 5e7ea88

File tree

5 files changed

+118
-16
lines changed

5 files changed

+118
-16
lines changed

benchmark/single-source/ExistentialPerformance.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import TestsUtils
1414

1515
public let ExistentialPerformance = [
16+
BenchmarkInfo(name: "DistinctClassFieldAccesses", runFunction: run_DistinctClassFieldAccesses, tags: [.unstable, .api, .Array]),
1617
BenchmarkInfo(name: "ExistentialTestArrayConditionalShift_ClassValueBuffer1", runFunction: run_ExistentialTestArrayConditionalShift_ClassValueBuffer1, tags: [.unstable, .api, .Array]),
1718
BenchmarkInfo(name: "ExistentialTestArrayConditionalShift_ClassValueBuffer2", runFunction: run_ExistentialTestArrayConditionalShift_ClassValueBuffer2, tags: [.unstable, .api, .Array]),
1819
BenchmarkInfo(name: "ExistentialTestArrayConditionalShift_ClassValueBuffer3", runFunction: run_ExistentialTestArrayConditionalShift_ClassValueBuffer3, tags: [.unstable, .api, .Array]),
@@ -114,6 +115,43 @@ public let ExistentialPerformance = [
114115
BenchmarkInfo(name: "ExistentialTestTwoMethodCalls_IntValueBuffer4", runFunction: run_ExistentialTestTwoMethodCalls_IntValueBuffer4, tags: [.unstable]),
115116
]
116117

118+
class ClassWithArrs {
119+
var N: Int = 0
120+
var A: [Int]
121+
var B: [Int]
122+
123+
init(N: Int) {
124+
self.N = N
125+
126+
A = [Int](repeating: 0, count: N)
127+
B = [Int](repeating: 0, count: N)
128+
}
129+
130+
func readArr() {
131+
for i in 0..<self.N {
132+
for j in 0..<i {
133+
let _ = A[j]
134+
let _ = B[j]
135+
}
136+
}
137+
}
138+
139+
func writeArr() {
140+
for i in 0..<self.N {
141+
A[i] = i
142+
B[i] = i
143+
}
144+
}
145+
}
146+
147+
public func run_DistinctClassFieldAccesses(_ N: Int) {
148+
let workload = ClassWithArrs(N: 100)
149+
for _ in 1...N {
150+
workload.writeArr()
151+
workload.readArr()
152+
}
153+
}
154+
117155
protocol Existential {
118156
init()
119157
func doIt() -> Bool

include/swift/SIL/MemAccessUtils.h

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,27 +29,44 @@ inline bool accessKindMayConflict(SILAccessKind a, SILAccessKind b) {
2929
}
3030

3131
/// Represents the identity of a stored class property as a combination
32-
/// of a base and a single projection. Eventually the goal is to make this
33-
/// more precise and consider, casts, etc.
32+
/// of a base, projection and projection path.
33+
/// we pre-compute the base and projection path, even though we can
34+
/// lazily do so, because it is more expensive otherwise
35+
/// We lazily compute the projection path,
36+
/// In the rare occasions we need it, because of Its destructor and
37+
/// its non-trivial copy constructor
3438
class ObjectProjection {
3539
SILValue object;
40+
const RefElementAddrInst *REA;
3641
Projection proj;
3742

3843
public:
39-
ObjectProjection(SILValue object, const Projection &proj)
40-
: object(object), proj(proj) {
41-
assert(object->getType().isObject());
42-
}
44+
ObjectProjection(const RefElementAddrInst *REA)
45+
: object(stripBorrow(REA->getOperand())), REA(REA),
46+
proj(Projection(REA)) {}
47+
48+
ObjectProjection(SILValue object, const RefElementAddrInst *REA)
49+
: object(object), REA(REA), proj(Projection(REA)) {}
50+
51+
const RefElementAddrInst *getInstr() const { return REA; }
4352

4453
SILValue getObject() const { return object; }
54+
4555
const Projection &getProjection() const { return proj; }
4656

57+
const ProjectionPath getProjectionPath() const {
58+
return ProjectionPath::getProjectionPath(stripBorrow(REA->getOperand()),
59+
REA)
60+
.getValue();
61+
}
62+
4763
bool operator==(const ObjectProjection &other) const {
48-
return object == other.object && proj == other.proj;
64+
return getObject() == other.getObject() &&
65+
getProjection() == other.getProjection();
4966
}
5067

5168
bool operator!=(const ObjectProjection &other) const {
52-
return object != other.object || proj != other.proj;
69+
return !operator==(other);
5370
}
5471
};
5572

@@ -168,8 +185,8 @@ class AccessedStorage {
168185

169186
AccessedStorage(SILValue base, Kind kind);
170187

171-
AccessedStorage(SILValue object, Projection projection)
172-
: objProj(object, projection) {
188+
AccessedStorage(SILValue object, const RefElementAddrInst *REA)
189+
: objProj(object, REA) {
173190
initKind(Class);
174191
}
175192

@@ -255,8 +272,19 @@ class AccessedStorage {
255272
}
256273

257274
bool isDistinctFrom(const AccessedStorage &other) const {
258-
return isUniquelyIdentified() && other.isUniquelyIdentified()
259-
&& !hasIdenticalBase(other);
275+
if (isUniquelyIdentified() && other.isUniquelyIdentified()) {
276+
return !hasIdenticalBase(other);
277+
}
278+
if (getKind() != Class || other.getKind() != Class) {
279+
return false;
280+
}
281+
if (getObjectProjection().getProjection() ==
282+
other.getObjectProjection().getProjection()) {
283+
return false;
284+
}
285+
auto projPath = getObjectProjection().getProjectionPath();
286+
auto otherProjPath = other.getObjectProjection().getProjectionPath();
287+
return projPath.hasNonEmptySymmetricDifference(otherProjPath);
260288
}
261289

262290
/// Returns the ValueDecl for the underlying storage, if it can be

lib/SIL/MemAccessUtils.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,7 @@ AccessedStorage::AccessedStorage(SILValue base, Kind kind) {
104104
// actually refer the same address) because these will be dynamically
105105
// checked.
106106
auto *REA = cast<RefElementAddrInst>(base);
107-
SILValue Object = stripBorrow(REA->getOperand());
108-
objProj = ObjectProjection(Object, Projection(REA));
107+
objProj = ObjectProjection(REA);
109108
}
110109
}
111110
}

lib/SILOptimizer/Analysis/AccessedStorageAnalysis.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,9 +258,9 @@ transformCalleeStorage(const StorageAccessInfo &storage,
258258
if (auto *arg = dyn_cast<SILFunctionArgument>(obj)) {
259259
SILValue argVal = getCallerArg(fullApply, arg->getIndex());
260260
if (argVal) {
261-
auto &proj = storage.getObjectProjection().getProjection();
261+
auto *instr = storage.getObjectProjection().getInstr();
262262
// Remap the argument source value and inherit the old storage info.
263-
return StorageAccessInfo(AccessedStorage(argVal, proj), storage);
263+
return StorageAccessInfo(AccessedStorage(argVal, instr), storage);
264264
}
265265
}
266266
// Otherwise, continue to reference the value in the callee because we don't

test/SILOptimizer/licm_exclusivity.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// RUN: %target-swift-frontend -O -enforce-exclusivity=checked -emit-sil -Xllvm -debug-only=sil-licm -primary-file %s 2>&1 | %FileCheck %s --check-prefix=TEST1
22
// RUN: %target-swift-frontend -O -enforce-exclusivity=checked -emit-sil -Xllvm -debug-only=sil-licm -primary-file %s 2>&1 | %FileCheck %s --check-prefix=TEST2
33
// RUN: %target-swift-frontend -O -enforce-exclusivity=checked -emit-sil -primary-file %s | %FileCheck %s --check-prefix=TESTSIL
4+
// RUN: %target-swift-frontend -O -enforce-exclusivity=checked -emit-sil -Xllvm -debug-only=sil-licm -whole-module-optimization %s 2>&1 | %FileCheck %s --check-prefix=TEST3
5+
// RUN: %target-swift-frontend -O -enforce-exclusivity=checked -emit-sil -whole-module-optimization %s | %FileCheck %s --check-prefix=TESTSIL2
6+
47
// REQUIRES: optimized_stdlib,asserts
58

69
// TEST1-LABEL: Processing loops in {{.*}}run_ReversedArray{{.*}}
@@ -44,3 +47,37 @@ public func count_unicodeScalars(_ s: String.UnicodeScalarView) {
4447
count += 1
4548
}
4649
}
50+
51+
52+
public class ClassWithArrs {
53+
var N: Int = 0
54+
var A: [Int]
55+
var B: [Int]
56+
57+
init(N: Int) {
58+
self.N = N
59+
60+
A = [Int](repeating: 0, count: N)
61+
B = [Int](repeating: 0, count: N)
62+
}
63+
64+
// TEST3-LABEL: Processing loops in {{.*}}ClassWithArrsC7readArr{{.*}}
65+
// TEST3: Hoist and Sink pairs attempt
66+
// TEST3: Hoisted
67+
// TEST3: Successfully hosited and sank pair
68+
// TEST3: Hoisted
69+
// TEST3: Successfully hosited and sank pair
70+
// TESTSIL2-LABEL: sil @$S16licm_exclusivity13ClassWithArrsC7readArryyF : $@convention(method) (@guaranteed ClassWithArrs) -> () {
71+
// TESTSIL2: [[R1:%.*]] = ref_element_addr %0 : $ClassWithArrs, #ClassWithArrs.A
72+
// TESTSIL2: [[R2:%.*]] = ref_element_addr %0 : $ClassWithArrs, #ClassWithArrs.B
73+
// TESTSIL2: begin_access [read] [static] [no_nested_conflict] [[R1]]
74+
// TESTSIL2: begin_access [read] [static] [no_nested_conflict] [[R2]]
75+
public func readArr() {
76+
for i in 0..<self.N {
77+
for j in 0..<i {
78+
let _ = A[j]
79+
let _ = B[j]
80+
}
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)