Skip to content

Commit 7a7237b

Browse files
authored
Merge pull request #35737 from eeckstein/doe-destroy-array
2 parents 74b7341 + 1655e22 commit 7a7237b

File tree

7 files changed

+413
-10
lines changed

7 files changed

+413
-10
lines changed

include/swift/SIL/MemAccessUtils.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,10 @@ class AccessPath {
821821
/// access, thus not within an access scope.
822822
static AccessPath computeInScope(SILValue address);
823823

824+
/// Creates an AccessPass, which identifies the first tail-element of the
825+
/// object \p rootReference.
826+
static AccessPath forTailStorage(SILValue rootReference);
827+
824828
// Encode a dynamic index_addr as an UnknownOffset.
825829
static constexpr int UnknownOffset = std::numeric_limits<int>::min() >> 1;
826830

@@ -939,7 +943,8 @@ class AccessPath {
939943

940944
bool operator==(AccessPath other) const {
941945
return
942-
storage.hasIdenticalBase(other.storage) && pathNode == other.pathNode;
946+
storage.hasIdenticalBase(other.storage) && pathNode == other.pathNode &&
947+
offset == other.offset;
943948
}
944949
bool operator!=(AccessPath other) const { return !(*this == other); }
945950

@@ -984,6 +989,12 @@ class AccessPath {
984989
SILFunction *function,
985990
unsigned useLimit = std::numeric_limits<unsigned>::max()) const;
986991

992+
/// Returns a new AccessPass, identical to this AccessPath, except that the
993+
/// offset is replaced with \p newOffset.
994+
AccessPath withOffset(int newOffset) const {
995+
return AccessPath(storage, pathNode, newOffset);
996+
}
997+
987998
void printPath(raw_ostream &os) const;
988999
void print(raw_ostream &os) const;
9891000
void dump() const;

lib/SIL/Utils/MemAccessUtils.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,13 @@ AccessedStorage AccessedStorage::computeInScope(SILValue sourceAddress) {
763763
// MARK: AccessPath
764764
//===----------------------------------------------------------------------===//
765765

766+
AccessPath AccessPath::forTailStorage(SILValue rootReference) {
767+
return AccessPath(
768+
AccessedStorage::forClass(rootReference, AccessedStorage::TailIndex),
769+
PathNode(rootReference->getModule()->getIndexTrieRoot()),
770+
/*offset*/ 0);
771+
}
772+
766773
bool AccessPath::contains(AccessPath subPath) const {
767774
if (!isValid() || !subPath.isValid()) {
768775
return false;

lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,31 @@ SILInstruction *SILCombiner::visitIndexAddrInst(IndexAddrInst *IA) {
997997
return Builder.createIndexAddr(IA->getLoc(), base2, newIndex);
998998
}
999999

1000+
/// Walks over all fields of an aggregate and checks if a reference count
1001+
/// operation for \p value is required. This differs from a simple `isTrivial`
1002+
/// check, because it treats a value_to_bridge_object instruction as "trivial".
1003+
static bool isTrivial(SILValue value, SILFunction *function) {
1004+
SmallVector<ValueBase *, 32> workList;
1005+
SmallPtrSet<ValueBase *, 16> visited;
1006+
workList.push_back(value);
1007+
while (!workList.empty()) {
1008+
SILValue v = workList.pop_back_val();
1009+
if (v->getType().isTrivial(*function))
1010+
continue;
1011+
if (isa<ValueToBridgeObjectInst>(v))
1012+
continue;
1013+
if (isa<StructInst>(v) || isa<TupleInst>(v)) {
1014+
for (SILValue op : cast<SingleValueInstruction>(v)->getOperandValues()) {
1015+
if (visited.insert(op).second)
1016+
workList.push_back(op);
1017+
}
1018+
continue;
1019+
}
1020+
return false;
1021+
}
1022+
return true;
1023+
}
1024+
10001025
SILInstruction *SILCombiner::visitReleaseValueInst(ReleaseValueInst *RVI) {
10011026
assert(!RVI->getFunction()->hasOwnership());
10021027

@@ -1031,7 +1056,7 @@ SILInstruction *SILCombiner::visitReleaseValueInst(ReleaseValueInst *RVI) {
10311056
RVI->getAtomicity());
10321057

10331058
// ReleaseValueInst of a trivial type is a no-op.
1034-
if (OperandTy.isTrivial(*RVI->getFunction()))
1059+
if (isTrivial(Operand, RVI->getFunction()))
10351060
return eraseInstFromFunction(*RVI);
10361061

10371062
// Do nothing for non-trivial non-reference types.

lib/SILOptimizer/Transforms/DeadObjectElimination.cpp

Lines changed: 116 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "swift/SIL/SILInstruction.h"
3737
#include "swift/SIL/SILModule.h"
3838
#include "swift/SIL/SILUndef.h"
39+
#include "swift/SIL/MemAccessUtils.h"
3940
#include "swift/SILOptimizer/Analysis/ArraySemantic.h"
4041
#include "swift/SILOptimizer/PassManager/Passes.h"
4142
#include "swift/SILOptimizer/PassManager/Transforms.h"
@@ -248,18 +249,112 @@ static bool canZapInstruction(SILInstruction *Inst, bool acceptRefCountInsts,
248249
return false;
249250
}
250251

252+
/// Returns true if all stores in \p users store to the tail elements of
253+
/// \p allocRef, which are destroyed by the \p destroyArray builtin.
254+
static bool onlyStoresToTailObjects(BuiltinInst *destroyArray,
255+
const UserList &users,
256+
AllocRefInst *allocRef) {
257+
// Get the number of destroyed elements.
258+
auto *literal = dyn_cast<IntegerLiteralInst>(destroyArray->getArguments()[2]);
259+
if (!literal || literal->getValue().getMinSignedBits() > 32)
260+
return false;
261+
int numDestroyed = literal->getValue().getSExtValue();
262+
263+
SILFunction *func = destroyArray->getFunction();
264+
SILBasicBlock *storesBlock = nullptr;
265+
266+
// Check if the destroyArray destroys the tail elements of allocRef.
267+
auto destroyPath = AccessPath::compute(destroyArray->getArguments()[1]);
268+
if (destroyPath != AccessPath::forTailStorage(allocRef))
269+
return false;
270+
271+
SmallVector<AccessPath, 32> pathsToCheck;
272+
273+
// Check all stores to the tail elements.
274+
for (SILInstruction *user : users) {
275+
auto *store = dyn_cast<StoreInst>(user);
276+
if (!store)
277+
continue;
278+
279+
assert(users.count(store->getSrc()->getDefiningInstruction()) == 0 &&
280+
"Storing a use of an array (that would mean the array escapes)?");
281+
282+
// All stores must be in the same block. This ensure that the stores
283+
// dominate the destroyArray (which may be in a different block).
284+
if (storesBlock && store->getParent() != storesBlock)
285+
return false;
286+
storesBlock = store->getParent();
287+
288+
AccessPath storePath = AccessPath::compute(store->getDest());
289+
if (!storePath.isValid())
290+
return false;
291+
292+
// We don't care about trivial stores.
293+
if (store->getSrc()->getType().isTrivial(*func))
294+
continue;
295+
296+
// Check if it's a store to the tail elements.
297+
if (!destroyPath.contains(storePath.withOffset(0)))
298+
return false;
299+
300+
// Check if the store is within the range of the destroyed array. In OSSA
301+
// we would not need this check. Otherwise it would be a memory lifetime
302+
// failure.
303+
if (storePath.getOffset() < 0 || storePath.getOffset() >= numDestroyed)
304+
return false;
305+
306+
pathsToCheck.push_back(storePath);
307+
}
308+
309+
// In non-OSSA we have to check if two paths overlap, because we could end up
310+
// over-releasing the stored objects.
311+
// Group the paths by tail-element index, so that we only have to check within
312+
// a tail-element group.
313+
std::sort(pathsToCheck.begin(), pathsToCheck.end(), [](AccessPath p1, AccessPath p2) {
314+
return p1.getOffset() < p2.getOffset();
315+
});
316+
for (unsigned i = 0, n = pathsToCheck.size(); i < n; ++i) {
317+
for (unsigned j = i + 1;
318+
j < n && pathsToCheck[i].getOffset() == pathsToCheck[j].getOffset(); ++j) {
319+
if (pathsToCheck[i].mayOverlap(pathsToCheck[j]))
320+
return false;
321+
// Limit the number of checks to avoid quadratic complexity.
322+
if (j > i + 8)
323+
return false;
324+
}
325+
}
326+
return true;
327+
}
328+
329+
static bool isDestroyArray(SILInstruction *inst) {
330+
BuiltinInst *bi = dyn_cast<BuiltinInst>(inst);
331+
return bi && bi->getBuiltinInfo().ID == BuiltinValueKind::DestroyArray;
332+
}
333+
334+
/// Inserts releases of all stores in \p users.
335+
static void insertCompensatingReleases(SILInstruction *before,
336+
const UserList &users) {
337+
for (SILInstruction *user : users) {
338+
if (auto *store = dyn_cast<StoreInst>(user)) {
339+
createDecrementBefore(store->getSrc(), before);
340+
}
341+
}
342+
}
343+
251344
/// Analyze the use graph of AllocRef for any uses that would prevent us from
252345
/// zapping it completely.
253346
static bool
254-
hasUnremovableUsers(SILInstruction *AllocRef, UserList *Users,
347+
hasUnremovableUsers(SILInstruction *allocation, UserList *Users,
255348
bool acceptRefCountInsts, bool onlyAcceptTrivialStores) {
256349
SmallVector<SILInstruction *, 16> Worklist;
257-
Worklist.push_back(AllocRef);
350+
Worklist.push_back(allocation);
258351

259352
LLVM_DEBUG(llvm::dbgs() << " Analyzing Use Graph.");
260353

261354
SmallVector<RefElementAddrInst *, 8> refElementAddrs;
262355
bool deallocationMaybeInlined = false;
356+
BuiltinInst *destroyArray = nullptr;
357+
AllocRefInst *allocRef = dyn_cast<AllocRefInst>(allocation);
263358

264359
while (!Worklist.empty()) {
265360
SILInstruction *I = Worklist.pop_back_val();
@@ -273,17 +368,19 @@ hasUnremovableUsers(SILInstruction *AllocRef, UserList *Users,
273368
continue;
274369
}
275370

276-
// If we can't zap this instruction... bail...
277-
if (!canZapInstruction(I, acceptRefCountInsts, onlyAcceptTrivialStores)) {
278-
LLVM_DEBUG(llvm::dbgs() << " Found instruction we can't zap...\n");
279-
return true;
280-
}
281-
282371
if (auto *rea = dyn_cast<RefElementAddrInst>(I)) {
283372
if (!rea->getType().isTrivial(*rea->getFunction()))
284373
refElementAddrs.push_back(rea);
285374
} else if (isa<SetDeallocatingInst>(I)) {
286375
deallocationMaybeInlined = true;
376+
} else if (allocRef && Users && isDestroyArray(I)) {
377+
if (destroyArray)
378+
return true;
379+
destroyArray = cast<BuiltinInst>(I);
380+
} else if (!canZapInstruction(I, acceptRefCountInsts,
381+
onlyAcceptTrivialStores)) {
382+
LLVM_DEBUG(llvm::dbgs() << " Found instruction we can't zap...\n");
383+
return true;
287384
}
288385

289386
// At this point, we can remove the instruction as long as all of its users
@@ -309,6 +406,12 @@ hasUnremovableUsers(SILInstruction *AllocRef, UserList *Users,
309406
}
310407
}
311408

409+
// In OSSA, we don't have to do this check. We can always accept a
410+
// destroyArray and insert the compensating destroys right at the store
411+
// instructions.
412+
if (destroyArray)
413+
return !onlyStoresToTailObjects(destroyArray, *Users, allocRef);
414+
312415
if (deallocationMaybeInlined) {
313416
// The alloc_ref is not destructed by a strong_release which is calling the
314417
// deallocator (destroying all stored properties).
@@ -767,6 +870,11 @@ bool DeadObjectElimination::processAllocRef(AllocRefInst *ARI) {
767870
return false;
768871
}
769872

873+
auto iter = std::find_if(UsersToRemove.begin(), UsersToRemove.end(),
874+
isDestroyArray);
875+
if (iter != UsersToRemove.end())
876+
insertCompensatingReleases(*iter, UsersToRemove);
877+
770878
// Remove the AllocRef and all of its users.
771879
removeInstructions(
772880
ArrayRef<SILInstruction*>(UsersToRemove.begin(), UsersToRemove.end()));

0 commit comments

Comments
 (0)