Skip to content

Commit edc600f

Browse files
committed
DeadObjectElimination: handle dead arrays for which the destructor is not inlined
So far, DeadObjectElimination could remove dead arrays if the destructor of the buffer is inlined and the "destroyArray" builtin is visible in the array's user list. Now, don't rely of inlining the destructor, but instead extend the destructor analysis to find the "destroyArray". https://bugs.swift.org/browse/SR-14774 rdar://79302972
1 parent 53fc177 commit edc600f

File tree

2 files changed

+114
-26
lines changed

2 files changed

+114
-26
lines changed

lib/SILOptimizer/Transforms/DeadObjectElimination.cpp

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ STATISTIC(DeadAllocApplyEliminated,
6262

6363
using UserList = llvm::SmallSetVector<SILInstruction *, 16>;
6464

65+
namespace {
66+
67+
/// Side effects of a destructor.
68+
enum class DestructorEffects {
69+
None,
70+
71+
/// The destructor contains a "destroyArray" builtin which destroys the tail
72+
/// elements of the object - like in Array.
73+
DestroysTailElems,
74+
75+
Unknown
76+
};
77+
6578
// Analyzing the body of this class destructor is valid because the object is
6679
// dead. This means that the object is never passed to objc_setAssociatedObject,
6780
// so its destructor cannot be extended at runtime.
@@ -98,14 +111,21 @@ static SILFunction *getDestructor(AllocRefInst *ARI) {
98111
return Fn;
99112
}
100113

114+
static bool isDestroyArray(SILInstruction *inst) {
115+
BuiltinInst *bi = dyn_cast<BuiltinInst>(inst);
116+
return bi && bi->getBuiltinInfo().ID == BuiltinValueKind::DestroyArray;
117+
}
118+
101119
/// Analyze the destructor for the class of ARI to see if any instructions in it
102120
/// could have side effects on the program outside the destructor. If it does
103121
/// not, then we can eliminate the destructor.
104-
static bool doesDestructorHaveSideEffects(AllocRefInst *ARI) {
122+
static DestructorEffects doesDestructorHaveSideEffects(AllocRefInst *ARI) {
105123
SILFunction *Fn = getDestructor(ARI);
106124
// If we can't find a constructor then assume it has side effects.
107125
if (!Fn)
108-
return true;
126+
return DestructorEffects::Unknown;
127+
128+
DestructorEffects effects = DestructorEffects::None;
109129

110130
// A destructor only has one argument, self.
111131
assert(Fn->begin()->getNumArguments() == 1 &&
@@ -115,7 +135,7 @@ static bool doesDestructorHaveSideEffects(AllocRefInst *ARI) {
115135
LLVM_DEBUG(llvm::dbgs() << " Analyzing destructor.\n");
116136

117137
// For each BB in the destructor...
118-
for (auto &BB : *Fn)
138+
for (auto &BB : *Fn) {
119139
// For each instruction I in BB...
120140
for (auto &I : BB) {
121141
LLVM_DEBUG(llvm::dbgs() << " Visiting: " << I);
@@ -127,6 +147,14 @@ static bool doesDestructorHaveSideEffects(AllocRefInst *ARI) {
127147
continue;
128148
}
129149

150+
if (auto *fl = dyn_cast<FixLifetimeInst>(&I)) {
151+
// A fix_lifetime of self does cannot have a side effect, because in the
152+
// destructor, Self is deleted.
153+
if (stripCasts(fl->getOperand()) == Self)
154+
continue;
155+
return DestructorEffects::Unknown;
156+
}
157+
130158
// RefCounting operations on Self are ok since we are already in the
131159
// destructor. RefCountingOperations on other instructions could have side
132160
// effects though.
@@ -142,7 +170,7 @@ static bool doesDestructorHaveSideEffects(AllocRefInst *ARI) {
142170
} else {
143171
LLVM_DEBUG(llvm::dbgs() << " UNSAFE! Ref count operation "
144172
"not on self.\n");
145-
return true;
173+
return DestructorEffects::Unknown;
146174
}
147175
}
148176

@@ -162,7 +190,7 @@ static bool doesDestructorHaveSideEffects(AllocRefInst *ARI) {
162190
} else {
163191
LLVM_DEBUG(llvm::dbgs() << " UNSAFE! dealloc_ref on value "
164192
"besides self.\n");
165-
return true;
193+
return DestructorEffects::Unknown;
166194
}
167195
}
168196

@@ -174,13 +202,28 @@ static bool doesDestructorHaveSideEffects(AllocRefInst *ARI) {
174202
continue;
175203
}
176204

205+
if (isDestroyArray(&I)) {
206+
// Check if the "destroyArray" destroys the tail elements of the object,
207+
// like in Array.
208+
SILValue addr = I.getOperand(1);
209+
auto *atp = dyn_cast<AddressToPointerInst>(addr);
210+
if (!atp)
211+
return DestructorEffects::Unknown;
212+
auto *rta = dyn_cast<RefTailAddrInst>(atp->getOperand());
213+
if (!rta)
214+
return DestructorEffects::Unknown;
215+
effects = DestructorEffects::DestroysTailElems;
216+
if (rta->getOperand() == Self)
217+
continue;
218+
}
219+
177220
LLVM_DEBUG(llvm::dbgs() << " UNSAFE! Unknown instruction.\n");
178221
// Otherwise, we can't remove the deallocation completely.
179-
return true;
222+
return DestructorEffects::Unknown;
180223
}
181-
224+
}
182225
// We didn't find any side effects.
183-
return false;
226+
return effects;
184227
}
185228

186229
//===----------------------------------------------------------------------===//
@@ -317,11 +360,6 @@ static bool onlyStoresToTailObjects(BuiltinInst *destroyArray,
317360
return true;
318361
}
319362

320-
static bool isDestroyArray(SILInstruction *inst) {
321-
BuiltinInst *bi = dyn_cast<BuiltinInst>(inst);
322-
return bi && bi->getBuiltinInfo().ID == BuiltinValueKind::DestroyArray;
323-
}
324-
325363
/// Inserts releases of all stores in \p users.
326364
static void insertCompensatingReleases(SILInstruction *before,
327365
const UserList &users) {
@@ -424,7 +462,6 @@ hasUnremovableUsers(SILInstruction *allocation, UserList *Users,
424462
// NonTrivial DeadObject Elimination
425463
//===----------------------------------------------------------------------===//
426464

427-
namespace {
428465
/// Determine if an object is dead. Compute its original lifetime. Find the
429466
/// lifetime endpoints reached by each store of a refcounted object into the
430467
/// object.
@@ -687,7 +724,8 @@ static bool isAllocatingApply(SILInstruction *Inst) {
687724

688725
namespace {
689726
class DeadObjectElimination : public SILFunctionTransform {
690-
llvm::DenseMap<SILType, bool> DestructorAnalysisCache;
727+
728+
llvm::DenseMap<SILType, DestructorEffects> DestructorAnalysisCache;
691729

692730
InstructionDeleter deleter;
693731

@@ -754,12 +792,12 @@ DeadObjectElimination::removeInstructions(ArrayRef<SILInstruction*> toRemove) {
754792
bool DeadObjectElimination::processAllocRef(AllocRefInst *ARI) {
755793
// Ok, we have an alloc_ref. Check the cache to see if we have already
756794
// computed the destructor behavior for its SILType.
757-
bool HasSideEffects;
795+
DestructorEffects destructorEffects;
758796
SILType Type = ARI->getType();
759797
auto CacheSearchResult = DestructorAnalysisCache.find(Type);
760798
if (CacheSearchResult != DestructorAnalysisCache.end()) {
761799
// Ok we found a value in the cache.
762-
HasSideEffects = CacheSearchResult->second;
800+
destructorEffects = CacheSearchResult->second;
763801
} else {
764802
// We did not find a value in the cache for our destructor. Analyze the
765803
// destructor to make sure it has no side effects. For now this only
@@ -769,24 +807,34 @@ bool DeadObjectElimination::processAllocRef(AllocRefInst *ARI) {
769807
//
770808
// TODO: We should be able to handle destructors that do nothing but release
771809
// members of the object.
772-
HasSideEffects = doesDestructorHaveSideEffects(ARI);
773-
DestructorAnalysisCache[Type] = HasSideEffects;
810+
destructorEffects = doesDestructorHaveSideEffects(ARI);
811+
DestructorAnalysisCache[Type] = destructorEffects;
774812
}
775813

776814
// Our destructor has no side effects, so if we can prove that no loads
777815
// escape, then we can completely remove the use graph of this alloc_ref.
778816
UserList UsersToRemove;
779817
if (hasUnremovableUsers(ARI, &UsersToRemove,
780-
/*acceptRefCountInsts=*/ !HasSideEffects,
781-
/*onlyAcceptTrivialStores*/false)) {
818+
/*acceptRefCountInsts=*/ destructorEffects != DestructorEffects::Unknown,
819+
/*onlyAcceptTrivialStores*/false)) {
782820
LLVM_DEBUG(llvm::dbgs() << " Found a use that cannot be zapped...\n");
783821
return false;
784822
}
785823

786-
auto iter = std::find_if(UsersToRemove.begin(), UsersToRemove.end(),
787-
isDestroyArray);
788-
if (iter != UsersToRemove.end())
789-
insertCompensatingReleases(*iter, UsersToRemove);
824+
// Find the instruction which releases the object's tail elements.
825+
SILInstruction *releaseOfTailElems = nullptr;
826+
for (SILInstruction *user : UsersToRemove) {
827+
if (isDestroyArray(user) ||
828+
(destructorEffects == DestructorEffects::DestroysTailElems &&
829+
isa<RefCountingInst>(user) && user->mayRelease())) {
830+
// Bail if we find multiple such instructions.
831+
if (releaseOfTailElems)
832+
return false;
833+
releaseOfTailElems = user;
834+
}
835+
}
836+
if (releaseOfTailElems)
837+
insertCompensatingReleases(releaseOfTailElems, UsersToRemove);
790838

791839
// Remove the AllocRef and all of its users.
792840
removeInstructions(

test/SILOptimizer/dead_array_elim.swift

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ func testDeadArrayElimWithAddressOnlyValues<T>(x: T, y: T) {
5959

6060
// CHECK-LABEL: sil hidden @$s15dead_array_elim31testDeadArrayAfterOptimizationsySiSSF
6161
// CHECK: bb0(%0 : $String):
62-
// CHECK-NEXT: debug_value {{.*}} name "stringParameter"
6362
// CHECK-NEXT: integer_literal $Builtin.Int{{[0-9]+}}, 21
6463
// CHECK-NEXT: struct $Int
6564
// CHECK-NEXT: return
@@ -78,4 +77,45 @@ func testDeadArrayAfterOptimizations(_ stringParameter: String) -> Int {
7877
return sum
7978
}
8079

80+
// CHECK-LABEL: sil hidden @$s15dead_array_elim15testNestedArraySiyF
81+
// CHECK: bb0:
82+
// CHECK-NEXT: integer_literal $Builtin.Int{{[0-9]+}}, 3
83+
// CHECK-NEXT: struct $Int
84+
// CHECK-NEXT: return
85+
// CHECK: } // end sil function '$s15dead_array_elim15testNestedArraySiyF'
86+
func testNestedArray() -> Int {
87+
struct Str {
88+
var sa: [String]
89+
var s: String? = nil
90+
var b: Bool = true
91+
92+
static func opt1(_ sa: [String], _ s: String?) -> Self {
93+
return .init(sa: sa, s: s, b: true)
94+
}
95+
96+
static func opt2(_ s1: String, _ s2: String? = nil) -> Self {
97+
return .opt1([s1], s2)
98+
}
99+
100+
static func opt3(_ sa: [String], _ s: String) -> Self {
101+
return .init(sa: sa, s: s, b: false)
102+
}
103+
}
104+
105+
let strArr: [Str] = [
106+
.opt1(["1", "2"], "3"),
107+
.opt3(["4", "5"], "6"),
108+
109+
.opt2("7"),
110+
.opt2("8"),
111+
]
112+
113+
var num = 0
114+
for s in strArr {
115+
if s.b {
116+
num += 1
117+
}
118+
}
119+
return num
120+
}
81121

0 commit comments

Comments
 (0)