Skip to content

Commit 85b2db4

Browse files
authored
Merge pull request swiftlang#37947 from eeckstein/dead-array-elimination
DeadObjectElimination: handle dead arrays for which the destructor is not inlined
2 parents dc0301d + edc600f commit 85b2db4

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)