Skip to content

Commit f6624e3

Browse files
committed
Add EscapeAnalysis::findObjectKind to mitigate deinit side effects
Deinitializer side effects severely handicap the connection-graph based EscapeAnalysis. Because it's not flow-sensitive, this approach falls apart whenever an object is released in the current function, which makes it seem to have escaped everywhere (it generally doesn't matter if it doesn't escape until the release point, but the analysis can't discern that). This can be slightly mitigated by realizing that releasing an object can only cause things it points to to escape if the object itself has references or pointers. Fix: Don't create a escaping content node when releasing an object that can't contain any references or pointers. The previous commit, "Fix EscapeAnalysis::mayReleaseContent" would defeat release-hoisting in important cases without doing anything else. Adding this extra precision to the connection graph avoids some regressions.
1 parent 23696e4 commit f6624e3

File tree

6 files changed

+186
-22
lines changed

6 files changed

+186
-22
lines changed

include/swift/SILOptimizer/Analysis/EscapeAnalysis.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,8 +989,13 @@ class EscapeAnalysis : public BottomUpIPAnalysis {
989989
/// The allocator for the connection graphs in Function2ConGraph.
990990
llvm::SpecificBumpPtrAllocator<FunctionInfo> Allocator;
991991

992+
// TODO: Use per-function caches, at least for Resilient types, to use Maximal
993+
// expansion.
994+
992995
/// Cache for isPointer().
993996
PointerKindCache pointerKindCache;
997+
/// Cache for checking the aggregate pointerness of class properties.
998+
PointerKindCache classPropertiesKindCache;
994999

9951000
SILModule *M;
9961001

@@ -1014,6 +1019,12 @@ class EscapeAnalysis : public BottomUpIPAnalysis {
10141019

10151020
PointerKind findCachedPointerKind(SILType Ty, const SILFunction &F) const;
10161021

1022+
PointerKind findClassPropertiesPointerKind(SILType Ty,
1023+
const SILFunction &F) const;
1024+
1025+
PointerKind findCachedClassPropertiesKind(SILType Ty,
1026+
const SILFunction &F) const;
1027+
10171028
// Returns true if the type \p Ty must be a reference or must transitively
10181029
// contain a reference and no other pointer or address type.
10191030
bool hasReferenceOnly(SILType Ty, const SILFunction &F) const {

lib/SILOptimizer/Analysis/AliasAnalysis.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -750,12 +750,18 @@ bool AliasAnalysis::canBuiltinDecrementRefCount(BuiltinInst *BI, SILValue Ptr) {
750750
continue;
751751

752752
// A builtin can only release an object if it can escape to one of the
753-
// builtin's arguments. 'EscapeAnalysis::mayReleaseContent()' expects 'Arg'
754-
// to be an owned reference and disallows addresses. Conservatively handle
755-
// address type arguments as and conservatively treat all other values
756-
// potential owned references.
757-
if (Arg->getType().isAddress() || EA->mayReleaseReferenceContent(Arg, Ptr))
758-
return true;
753+
// builtin's arguments.
754+
if (Arg->getType().isAddress()) {
755+
// Handle indirect argument as if they are a release to any references
756+
// pointed to by the argument's address.
757+
if (EA->mayReleaseAddressContent(Arg, Ptr))
758+
return true;
759+
} else {
760+
// Handle direct arguments as if they are a direct release of the
761+
// reference (just like a destroy_value).
762+
if (EA->mayReleaseReferenceContent(Arg, Ptr))
763+
return true;
764+
}
759765
}
760766
return false;
761767
}

lib/SILOptimizer/Analysis/EscapeAnalysis.cpp

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,54 @@ EscapeAnalysis::findRecursivePointerKind(SILType Ty,
101101
return NoPointer;
102102
}
103103

104+
// Return the PointerKind that summarizes a class's stored properties.
105+
//
106+
// If a class only holds fields of non-pointer types, then it is guaranteed not
107+
// to point to any other objects.
108+
EscapeAnalysis::PointerKind
109+
EscapeAnalysis::findClassPropertiesPointerKind(SILType Ty,
110+
const SILFunction &F) const {
111+
if (Ty.isAddress())
112+
return AnyPointer;
113+
114+
auto *classDecl = Ty.getClassOrBoundGenericClass();
115+
if (!classDecl)
116+
return AnyPointer;
117+
118+
auto &M = F.getModule();
119+
auto expansion = F.getTypeExpansionContext();
120+
121+
// Start with the most precise pointer kind
122+
PointerKind propertiesKind = NoPointer;
123+
auto meetAggregateKind = [&](PointerKind otherKind) {
124+
if (otherKind > propertiesKind)
125+
propertiesKind = otherKind;
126+
};
127+
for (Type classTy = Ty.getASTType(); classTy;
128+
classTy = classTy->getSuperclass()) {
129+
classDecl = classTy->getClassOrBoundGenericClass();
130+
assert(classDecl && "superclass must be a class");
131+
132+
// Return AnyPointer unless we have guaranteed visibility into all class and
133+
// superclass properties. Use Minimal resilience expansion because the cache
134+
// is not per-function.
135+
if (classDecl->isResilient())
136+
return AnyPointer;
137+
138+
// For each field in the class, get the pointer kind for that field. For
139+
// reference-type properties, this will be ReferenceOnly. For aggregates, it
140+
// will be the meet over all aggregate fields.
141+
SILType objTy =
142+
SILType::getPrimitiveObjectType(classTy->getCanonicalType());
143+
for (VarDecl *property : classDecl->getStoredProperties()) {
144+
SILType fieldTy =
145+
objTy.getFieldType(property, M, expansion).getObjectType();
146+
meetAggregateKind(findCachedPointerKind(fieldTy, F));
147+
}
148+
}
149+
return propertiesKind;
150+
}
151+
104152
// Returns the kind of pointer that \p Ty recursively contains.
105153
EscapeAnalysis::PointerKind
106154
EscapeAnalysis::findCachedPointerKind(SILType Ty, const SILFunction &F) const {
@@ -113,6 +161,19 @@ EscapeAnalysis::findCachedPointerKind(SILType Ty, const SILFunction &F) const {
113161
return pointerKind;
114162
}
115163

164+
EscapeAnalysis::PointerKind
165+
EscapeAnalysis::findCachedClassPropertiesKind(SILType Ty,
166+
const SILFunction &F) const {
167+
auto iter = classPropertiesKindCache.find(Ty);
168+
if (iter != classPropertiesKindCache.end())
169+
return iter->second;
170+
171+
PointerKind pointerKind = findClassPropertiesPointerKind(Ty, F);
172+
const_cast<EscapeAnalysis *>(this)
173+
->classPropertiesKindCache[Ty] = pointerKind;
174+
return pointerKind;
175+
}
176+
116177
// If EscapeAnalysis should consider the given value to be a derived address or
117178
// pointer based on one of its address or pointer operands, then return that
118179
// operand value. Otherwise, return an invalid value.
@@ -2124,11 +2185,18 @@ void EscapeAnalysis::analyzeInstruction(SILInstruction *I,
21242185
if (!fieldNode) {
21252186
// In the unexpected case that the object has no field content, create
21262187
// escaping unknown content.
2188+
//
2189+
// TODO: Why does this need to be "escaping"? The fields can't escape
2190+
// during deinitialization.
21272191
ConGraph->getOrCreateUnknownContent(objNode)->markEscaping();
21282192
return;
21292193
}
21302194
if (!deinitIsKnownToNotCapture(OpV)) {
2131-
ConGraph->getOrCreateUnknownContent(fieldNode)->markEscaping();
2195+
// Find out if the object's fields may have any references or pointers.
2196+
PointerKind propertiesKind =
2197+
findCachedClassPropertiesKind(OpV->getType(), *OpV->getFunction());
2198+
if (propertiesKind != EscapeAnalysis::NoPointer)
2199+
ConGraph->getOrCreateUnknownContent(fieldNode)->markEscaping();
21322200
return;
21332201
}
21342202
// This deinit is known to not directly capture it's own field content;

test/SILOptimizer/arcsequenceopts.sil

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -630,9 +630,18 @@ bb0(%0 : $Cls):
630630
// 'Container' object, so the child will be destroyed with the parent.
631631
// The original SIL already has an over-release of the parent.
632632
//
633+
// TODO: This optimization is currently defeated because
634+
// - release_container marks anything '%2 = alloc_ref $Container' points to as escaping
635+
// - in this function, '%2 = alloc_ref $Container' points to '%1 = alloc_ref $Cls'
636+
// - therefore, the 'apply' may potentially release '%1 = alloc_ref $Cls'
637+
//
638+
// To hoist this 'release %1 : $Cls', AliasAnalysis alsos need to prove that
639+
// it isn't being hoisted above the last use. This information is not
640+
// properly communicated between AliasAnalysis and EscapeAnalysis.
641+
//
633642
// CHECK-LABEL: sil @remove_as_local_object_indirectly_escapes_to_callee
634-
// CHECK-NOT: retain_value
635-
// CHECK-NOT: release_value
643+
// CHECK: retain_value
644+
// CHECK: release_value
636645
sil @remove_as_local_object_indirectly_escapes_to_callee : $@convention(thin) (Cls) -> () {
637646
bb0(%0 : $Cls):
638647
%1 = alloc_ref $Cls

test/SILOptimizer/escape_analysis.sil

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Builtin
88
import Swift
99
import SwiftShims
1010

11-
protocol ClassP : class {
11+
protocol ClassP : AnyObject {
1212
func foo()
1313
}
1414

@@ -1774,7 +1774,7 @@ class IntWrapper {
17741774

17751775
// CHECK-LABEL: CG of testInteriorCycle
17761776
// CHECK-NEXT: Arg [ref] %0 Esc: A, Succ: (%2)
1777-
// CHECK-NEXT: Con [int] %2 Esc: G, Succ: (%2)
1777+
// CHECK-NEXT: Con [int] %2 Esc: A, Succ: (%2)
17781778
// CHECK-NEXT: Val %6 Esc: , Succ: %0, %2
17791779
// CHECK-NEXT: Ret return Esc: , Succ: %6
17801780
// CHECK-LABEL: End

test/SILOptimizer/late_release_hoisting.sil

Lines changed: 81 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ class HasInt64 {
1717
var i : Int64
1818
}
1919

20+
class HasInt64Sub : HasObj {
21+
var i : Int64
22+
}
23+
24+
class HasHasInt64 : HasObj {
25+
var hi : HasInt64
26+
}
27+
2028
class HasHasObj {
2129
var ho : HasObj
2230
}
@@ -26,10 +34,10 @@ class HasHasObj {
2634
//
2735
// CHECK-LABEL: sil @testLocalNotReachesEscaped : $@convention(thin) (Int64, @owned HasObj) -> AnyObject {
2836
// CHECK: bb0(%0 : $Int64, %1 : $HasObj):
29-
// CHECK: [[LO:%.*]] = alloc_ref $HasInt
37+
// CHECK: [[LO:%.*]] = alloc_ref $HasInt64
3038
// CHECK: [[IADR:%.*]] = ref_element_addr [[LO]] : $HasInt64, #HasInt64.i
3139
// CHECK: store %0 to [[IADR]] : $*Int64
32-
// CHECK: strong_release [[LO]] : $HasInt
40+
// CHECK: strong_release [[LO]] : $HasInt64
3341
// CHECK: [[OADR:%.*]] = ref_element_addr %1 : $HasObj, #HasObj.o
3442
// CHECK: [[O:%.*]] = load [[OADR]] : $*AnyObject
3543
// CHECK: return [[O]] : $AnyObject
@@ -39,13 +47,39 @@ bb0(%0 : $Int64, %1 : $HasObj):
3947
%lo = alloc_ref $HasInt64
4048
%iadr = ref_element_addr %lo : $HasInt64, #HasInt64.i
4149
store %0 to %iadr : $*Int64
42-
4350
%oadr = ref_element_addr %1 : $HasObj, #HasObj.o
4451
%o = load %oadr : $*AnyObject
4552
strong_release %lo : $HasInt64
4653
return %o : $AnyObject
4754
}
4855

56+
// The release of %lo cannot be hoisted over the load because it the
57+
// release of %lo causes all objects pointed to by its fields to
58+
// escape. Since it has at least one reference-type field, it may
59+
// point to escaping memory.
60+
//
61+
// CHECK-LABEL: sil @testLocalMayReachEscaped : $@convention(thin) (Int64, @owned HasObj) -> AnyObject {
62+
// CHECK: bb0(%0 : $Int64, %1 : $HasObj):
63+
// CHECK: [[LO:%.*]] = alloc_ref $HasInt64Sub
64+
// CHECK: [[IADR:%.*]] = ref_element_addr [[LO]] : $HasInt64Sub, #HasInt64Sub.i
65+
// CHECK: store %0 to [[IADR]] : $*Int64
66+
// CHECK: [[OADR:%.*]] = ref_element_addr %1 : $HasObj, #HasObj.o
67+
// CHECK: [[O:%.*]] = load [[OADR]] : $*AnyObject
68+
// CHECK: strong_release [[LO]] : $HasInt64Sub
69+
// CHECK: return [[O]] : $AnyObject
70+
// CHECK-LABEL: } // end sil function 'testLocalMayReachEscaped'
71+
sil @testLocalMayReachEscaped : $@convention(thin) (Int64, @owned HasObj) -> AnyObject {
72+
bb0(%0 : $Int64, %1 : $HasObj):
73+
%lo = alloc_ref $HasInt64Sub
74+
%iadr = ref_element_addr %lo : $HasInt64Sub, #HasInt64Sub.i
75+
store %0 to %iadr : $*Int64
76+
77+
%oadr = ref_element_addr %1 : $HasObj, #HasObj.o
78+
%o = load %oadr : $*AnyObject
79+
strong_release %lo : $HasInt64Sub
80+
return %o : $AnyObject
81+
}
82+
4983
// The release of %lo cannot be hoisted over the load.
5084
//
5185
// CHECK-LABEL: sil @testLocalReachesEscaped : $@convention(thin) (@owned AnyObject) -> AnyObject {
@@ -70,10 +104,12 @@ bb0(%0 : $AnyObject):
70104
return %o : $AnyObject
71105
}
72106

73-
// The release of %lo can be hoisted above the load from %oadr. There
74-
// is a points-to relation between %lo and %oadr. However, the
75-
// points-to path from %lo to %oadr reaches another reference counted
76-
// object before reaching $oadr.
107+
// The release of %lo cannot be hoisted above the load from %oadr
108+
// because there is a points-to relation between %lo and %oadr.
109+
//
110+
// TODO: To hoist this release, AliasAnalysis also need to prove that
111+
// it isn't being hoisted above the last use. This information is not
112+
// properly communicated between AliasAnalysis and EscapeAnalysis.
77113
//
78114
// CHECK-LABEL: sil @testLocalReachesRCEscaped : $@convention(thin) (@owned HasObj) -> AnyObject {
79115
// CHECK: bb0(%0 : $HasObj):
@@ -82,9 +118,9 @@ bb0(%0 : $AnyObject):
82118
// CHECK: strong_retain %0 : $HasObj
83119
// CHECK: store %0 to [[HOADR]] : $*HasObj
84120
// CHECK: [[HO:%.*]] = load [[HOADR]] : $*HasObj
85-
// CHECK: strong_release [[LO]] : $HasHasObj
86121
// CHECK: [[OADR:%.*]] = ref_element_addr [[HO]] : $HasObj, #HasObj.o
87122
// CHECK: [[O:%.*]] = load [[OADR]] : $*AnyObject
123+
// CHECK: strong_release [[LO]] : $HasHasObj
88124
// CHECK: return [[O]] : $AnyObject
89125
// CHECK-LABEL: } // end sil function 'testLocalReachesRCEscaped'
90126
sil @testLocalReachesRCEscaped : $@convention(thin) (@owned HasObj) -> AnyObject {
@@ -144,9 +180,13 @@ bb0(%0 : $AnyObject):
144180
return %o : $AnyObject
145181
}
146182

147-
// Two local references, one reachable from the other. We assume that
183+
// Two local references, one reachable from the other. We cannot assume that
148184
// the reachable one has its own reference count and hoist
149-
// strong_release %hho above load %oadr.
185+
// strong_release %hho above load %oadr without more information.
186+
//
187+
// TODO: To hoist this release, AliasAnalysis also need to prove that
188+
// it isn't being hoisted above the last use. This information is not
189+
// properly communicated between AliasAnalysis and EscapeAnalysis.
150190
//
151191
// CHECK-LABEL: sil @testLocalReachesRCLocal : $@convention(thin) (AnyObject) -> AnyObject {
152192
// CHECK: bb0(%0 : $AnyObject):
@@ -158,9 +198,9 @@ bb0(%0 : $AnyObject):
158198
// CHECK: [[HOADR:%.*]] = ref_element_addr [[HHO]] : $HasHasObj, #HasHasObj.ho
159199
// CHECK: store [[LO]] to [[HOADR]] : $*HasObj
160200
// CHECK: [[HO:%.*]] = load [[HOADR]] : $*HasObj
161-
// CHECK: strong_release [[HHO]] : $HasHasObj
162201
// CHECK: [[OADR2:%.*]] = ref_element_addr [[HO]] : $HasObj, #HasObj.o
163202
// CHECK: [[O:%.*]] = load [[OADR2]] : $*AnyObject
203+
// CHECK: strong_release [[HHO]] : $HasHasObj
164204
// CHECK: strong_retain [[O]] : $AnyObject
165205
// CHECK: return [[O]] : $AnyObject
166206
// CHECK-LABEL: } // end sil function 'testLocalReachesRCLocal'
@@ -232,3 +272,33 @@ bb0(%0 : $AnyObject, %1 : $HasHasObj):
232272
strong_release %1 : $HasHasObj
233273
return %o : $AnyObject
234274
}
275+
276+
// 'strong_release %lo : $HasHasInt64' cannot be hoisted because it
277+
// contains a reference that appears to escape. With the current
278+
// information, we can't be sure if that escaping reference points to
279+
// the load of '%oadr'.
280+
//
281+
// TODO: To hoist this release, AliasAnalysis also need to prove that
282+
// it isn't being hoisted above the last use. This information is not
283+
// properly communicated between AliasAnalysis and EscapeAnalysis.
284+
//
285+
// CHECK-LABEL: sil @testLocalWithReferenceProperty : $@convention(thin) (Int64, @owned HasObj) -> AnyObject {
286+
// CHECK: bb0(%0 : $Int64, %1 : $HasObj):
287+
// CHECK: [[O:%.*]] = load {{.*}} : $*AnyObject
288+
// CHECK: strong_release %{{.*}} : $HasHasInt64
289+
// CHECK: return [[O]] : $AnyObject
290+
// CHECK-LABEL: } // end sil function 'testLocalWithReferenceProperty'
291+
sil @testLocalWithReferenceProperty : $@convention(thin) (Int64, @owned HasObj) -> AnyObject {
292+
bb0(%0 : $Int64, %1 : $HasObj):
293+
%hi = alloc_ref $HasInt64
294+
%iadr = ref_element_addr %hi : $HasInt64, #HasInt64.i
295+
store %0 to %iadr : $*Int64
296+
%lo = alloc_ref $HasHasInt64
297+
%hiadr = ref_element_addr %lo : $HasHasInt64, #HasHasInt64.hi
298+
store %hi to %hiadr : $*HasInt64
299+
300+
%oadr = ref_element_addr %1 : $HasObj, #HasObj.o
301+
%o = load %oadr : $*AnyObject
302+
strong_release %lo : $HasHasInt64
303+
return %o : $AnyObject
304+
}

0 commit comments

Comments
 (0)