Skip to content

Commit 1075f77

Browse files
authored
Merge pull request #67521 from meg-gupta/migratedoe
Migrate DeadObjectElimination to OSSA
2 parents bba2f47 + 996fd8b commit 1075f77

File tree

3 files changed

+887
-72
lines changed

3 files changed

+887
-72
lines changed

lib/SILOptimizer/Transforms/DeadObjectElimination.cpp

Lines changed: 180 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "swift/SIL/BasicBlockUtils.h"
3030
#include "swift/SIL/DebugUtils.h"
3131
#include "swift/SIL/InstructionUtils.h"
32+
#include "swift/SIL/OwnershipUtils.h"
3233
#include "swift/SIL/Projection.h"
3334
#include "swift/SIL/SILArgument.h"
3435
#include "swift/SIL/SILDeclRef.h"
@@ -127,6 +128,10 @@ static bool isDestroyArray(SILInstruction *inst) {
127128
/// Analyze the destructor for the class of ARI to see if any instructions in it
128129
/// could have side effects on the program outside the destructor. If it does
129130
/// not, then we can eliminate the destructor.
131+
/// TODO: Most default destructors with non-trivial elements will have a
132+
/// destroy_addr of the non-trivial element in the destructor, this analysis
133+
/// will return as having side-effects in such cases, leading to conservative
134+
/// results. Check if we can do better here.
130135
static DestructorEffects doesDestructorHaveSideEffects(AllocRefInstBase *ARI) {
131136
SILFunction *Fn = getDestructor(ARI);
132137
// If we can't find a constructor then assume it has side effects.
@@ -173,13 +178,22 @@ static DestructorEffects doesDestructorHaveSideEffects(AllocRefInstBase *ARI) {
173178
assert(RefInst->getNumOperands() == 1 &&
174179
"Make sure RefInst only has one argument.");
175180
LLVM_DEBUG(llvm::dbgs() << " SAFE! Ref count operation on "
176-
"Self.\n");
181+
"Self.\n");
182+
continue;
183+
}
184+
LLVM_DEBUG(llvm::dbgs() << " UNSAFE! Ref count operation "
185+
"not on self.\n");
186+
return DestructorEffects::Unknown;
187+
}
188+
if (auto *destroy = dyn_cast<DestroyValueInst>(&I)) {
189+
if (stripCasts(destroy->getOperand()) == Self) {
190+
LLVM_DEBUG(llvm::dbgs() << " SAFE! Ref count operation on "
191+
"Self.\n");
177192
continue;
178-
} else {
179-
LLVM_DEBUG(llvm::dbgs() << " UNSAFE! Ref count operation "
180-
"not on self.\n");
181-
return DestructorEffects::Unknown;
182193
}
194+
LLVM_DEBUG(llvm::dbgs() << " UNSAFE! Ref count operation "
195+
"not on self.\n");
196+
return DestructorEffects::Unknown;
183197
}
184198

185199
// dealloc_stack can be ignored.
@@ -242,7 +256,15 @@ static DestructorEffects doesDestructorHaveSideEffects(AllocRefInstBase *ARI) {
242256
/// alloc_ref alive.
243257
static bool canZapInstruction(SILInstruction *Inst, bool acceptRefCountInsts,
244258
bool onlyAcceptTrivialStores) {
245-
if (isa<SetDeallocatingInst>(Inst) || isa<FixLifetimeInst>(Inst))
259+
if (isa<DestroyValueInst>(Inst)) {
260+
return acceptRefCountInsts;
261+
}
262+
if (isa<CopyValueInst>(Inst) || isa<BeginBorrowInst>(Inst) ||
263+
isa<MoveValueInst>(Inst)) {
264+
return true;
265+
}
266+
if (isa<SetDeallocatingInst>(Inst) || isa<FixLifetimeInst>(Inst) ||
267+
isa<EndBorrowInst>(Inst))
246268
return true;
247269

248270
// It is ok to eliminate various retains/releases. We are either removing
@@ -267,13 +289,17 @@ static bool canZapInstruction(SILInstruction *Inst, bool acceptRefCountInsts,
267289
if (isa<DestroyAddrInst>(Inst))
268290
return true;
269291

270-
// If we see a store here, we have already checked that we are storing into
271-
// the pointer before we added it to the worklist, so we can skip it.
292+
// We have already checked that we are storing into the pointer before we
293+
// added it to the worklist. Here, in the case we are allowing non-trivial
294+
// stores, check if the store's source is lexical, if so return false.
295+
// Deleting a dead object with non-trivial stores, will need compensating
296+
// destroys at the store for it's source, which will shorten the lifetime of
297+
// the store's source.
272298
if (auto *store = dyn_cast<StoreInst>(Inst)) {
273-
// TODO: when we have OSSA, we can also accept stores of non trivial values:
274-
// just replace the store with a destroy_value.
275-
return !onlyAcceptTrivialStores ||
276-
store->getSrc()->getType().isTrivial(*store->getFunction());
299+
auto storeSrc = store->getSrc();
300+
return storeSrc->getType().isTrivial(*store->getFunction()) ||
301+
(!onlyAcceptTrivialStores &&
302+
(!store->getFunction()->hasOwnership() || !storeSrc->isLexical()));
277303
}
278304

279305
// Conceptually this instruction has no side-effects.
@@ -382,7 +408,7 @@ hasUnremovableUsers(SILInstruction *allocation, UserList *Users,
382408
LLVM_DEBUG(llvm::dbgs() << " Analyzing Use Graph.");
383409

384410
SmallVector<RefElementAddrInst *, 8> refElementAddrs;
385-
bool deallocationMaybeInlined = false;
411+
386412
BuiltinInst *destroyArray = nullptr;
387413
auto *allocRef = dyn_cast<AllocRefInstBase>(allocation);
388414

@@ -396,14 +422,10 @@ hasUnremovableUsers(SILInstruction *allocation, UserList *Users,
396422
if (Users && !Users->insert(I)) {
397423
LLVM_DEBUG(llvm::dbgs() << " Already seen skipping...\n");
398424
continue;
399-
}
400-
401-
if (auto *rea = dyn_cast<RefElementAddrInst>(I)) {
402-
if (!rea->getType().isTrivial(*rea->getFunction()))
425+
} else if (auto *rea = dyn_cast<RefElementAddrInst>(I)) {
426+
if (rea != allocation && !rea->getType().isTrivial(*rea->getFunction()))
403427
refElementAddrs.push_back(rea);
404-
} else if (isa<SetDeallocatingInst>(I)) {
405-
deallocationMaybeInlined = true;
406-
} else if (allocRef && Users && isDestroyArray(I)) {
428+
} else if (allocRef && isDestroyArray(I)) {
407429
if (destroyArray)
408430
return true;
409431
destroyArray = cast<BuiltinInst>(I);
@@ -436,18 +458,14 @@ hasUnremovableUsers(SILInstruction *allocation, UserList *Users,
436458
}
437459
}
438460

439-
// In OSSA, we don't have to do this check. We can always accept a
440-
// destroyArray and insert the compensating destroys right at the store
441-
// instructions.
442-
if (destroyArray)
443-
return !onlyStoresToTailObjects(destroyArray, *Users, allocRef);
444-
445-
if (deallocationMaybeInlined) {
446-
// The alloc_ref is not destructed by a strong_release which is calling the
447-
// deallocator (destroying all stored properties).
461+
if (!allocation->getFunction()->hasOwnership()) {
462+
// In non-ossa, if we found a destroy array builtin that destroys the tail
463+
// elements, ensure all stores are to the taile elems.
464+
if (destroyArray) {
465+
return !onlyStoresToTailObjects(destroyArray, *Users, allocRef);
466+
}
448467
// In non-OSSA we cannot reliably track the lifetime of non-trivial stored
449468
// properties. Removing the dead alloc_ref might leak a property value.
450-
// TODO: in OSSA we can replace stores to properties with a destroy_value.
451469
for (RefElementAddrInst *rea : refElementAddrs) {
452470
// Re-run the check with not accepting non-trivial stores.
453471
if (hasUnremovableUsers(rea, nullptr, acceptRefCountInsts,
@@ -567,9 +585,8 @@ recursivelyCollectInteriorUses(ValueBase *DefInst,
567585
auto User = Op->getUser();
568586

569587
// Lifetime endpoints that don't allow the address to escape.
570-
if (isa<RefCountingInst>(User) ||
571-
isa<DebugValueInst>(User) ||
572-
isa<FixLifetimeInst>(User)) {
588+
if (isa<RefCountingInst>(User) || isa<DebugValueInst>(User) ||
589+
isa<FixLifetimeInst>(User) || isa<DestroyValueInst>(User)) {
573590
AllUsers.insert(User);
574591
continue;
575592
}
@@ -704,10 +721,7 @@ static void insertReleases(ArrayRef<StoreInst*> Stores,
704721
// per block, and all release points occur after all stores. Therefore we
705722
// can simply ask SSAUpdater for the reaching store.
706723
SILValue RelVal = SSAUp.getValueAtEndOfBlock(RelPoint->getParent());
707-
if (StVal->getType().isReferenceCounted(RelPoint->getModule()))
708-
B.createStrongRelease(Loc, RelVal, B.getDefaultAtomicity());
709-
else
710-
B.createReleaseValue(Loc, RelVal, B.getDefaultAtomicity());
724+
B.emitDestroyValueOperation(Loc, RelVal);
711725
}
712726
}
713727

@@ -751,10 +765,11 @@ class DeadObjectElimination : public SILFunctionTransform {
751765
DeadEndBlocks DEBlocks(&Fn);
752766
DestructorAnalysisCache.clear();
753767

768+
LLVM_DEBUG(llvm::dbgs() << "Processing " << Fn.getName() << "\n");
769+
754770
bool Changed = false;
755771

756772
for (auto &BB : Fn) {
757-
758773
for (SILInstruction &inst : BB.deletableInstructions()) {
759774
if (auto *A = dyn_cast<AllocRefInstBase>(&inst))
760775
Changed |= processAllocRef(A);
@@ -773,10 +788,6 @@ class DeadObjectElimination : public SILFunctionTransform {
773788
}
774789

775790
void run() override {
776-
// FIXME: We should support ownership eventually.
777-
if (getFunction()->hasOwnership())
778-
return;
779-
780791
assert(!domInfo);
781792

782793
if (processFunction(*getFunction())) {
@@ -829,21 +840,48 @@ bool DeadObjectElimination::processAllocRef(AllocRefInstBase *ARI) {
829840
return false;
830841
}
831842

832-
// Find the instruction which releases the object's tail elements.
833-
SILInstruction *releaseOfTailElems = nullptr;
834-
for (SILInstruction *user : UsersToRemove) {
835-
if (isDestroyArray(user) ||
836-
(destructorEffects == DestructorEffects::DestroysTailElems &&
837-
isa<RefCountingInst>(user) && user->mayRelease())) {
838-
// Bail if we find multiple such instructions.
839-
if (releaseOfTailElems)
843+
if (!ARI->getFunction()->hasOwnership()) {
844+
// Find the instruction which releases the object's tail elements.
845+
SILInstruction *releaseOfTailElems = nullptr;
846+
for (SILInstruction *user : UsersToRemove) {
847+
if (isDestroyArray(user) ||
848+
(destructorEffects == DestructorEffects::DestroysTailElems &&
849+
isa<RefCountingInst>(user) && user->mayRelease())) {
850+
// Bail if we find multiple such instructions.
851+
if (releaseOfTailElems)
852+
return false;
853+
releaseOfTailElems = user;
854+
}
855+
}
856+
if (releaseOfTailElems) {
857+
if (!insertCompensatingReleases(releaseOfTailElems, UsersToRemove)) {
840858
return false;
841-
releaseOfTailElems = user;
859+
}
842860
}
843861
}
844-
if (releaseOfTailElems) {
845-
if (!insertCompensatingReleases(releaseOfTailElems, UsersToRemove)) {
846-
return false;
862+
863+
if (ARI->getFunction()->hasOwnership()) {
864+
// In ossa, we are going to delete the dead element store and insert a
865+
// destroy_value of the store's source. This is shortening the store's
866+
// source lifetime. Check if there was a pointer escape of the store's
867+
// source, if so bail out.
868+
for (auto *user : UsersToRemove) {
869+
auto *store = dyn_cast<StoreInst>(user);
870+
if (!store ||
871+
store->getOwnershipQualifier() == StoreOwnershipQualifier::Trivial)
872+
continue;
873+
if (findPointerEscape(store->getSrc())) {
874+
return false;
875+
}
876+
}
877+
for (auto *user : UsersToRemove) {
878+
auto *store = dyn_cast<StoreInst>(user);
879+
if (!store ||
880+
store->getOwnershipQualifier() == StoreOwnershipQualifier::Trivial) {
881+
continue;
882+
}
883+
SILBuilderWithScope(store).createDestroyValue(store->getLoc(),
884+
store->getSrc());
847885
}
848886
}
849887

@@ -859,13 +897,41 @@ bool DeadObjectElimination::processAllocRef(AllocRefInstBase *ARI) {
859897
bool DeadObjectElimination::processAllocStack(AllocStackInst *ASI) {
860898
// Trivial types don't have destructors.
861899
bool isTrivialType = ASI->getElementType().isTrivial(*ASI->getFunction());
900+
// In non-ossa, only accept trivial stores if we have a non-trivial
901+
// alloc_stack
902+
bool onlyAcceptTrivialStores =
903+
ASI->getFunction()->hasOwnership() ? false : !isTrivialType;
862904
UserList UsersToRemove;
863-
if (hasUnremovableUsers(ASI, &UsersToRemove, /*acceptRefCountInsts=*/ true,
864-
/*onlyAcceptTrivialStores*/!isTrivialType)) {
905+
if (hasUnremovableUsers(ASI, &UsersToRemove, /*acceptRefCountInsts=*/true,
906+
onlyAcceptTrivialStores)) {
865907
LLVM_DEBUG(llvm::dbgs() << " Found a use that cannot be zapped...\n");
866908
return false;
867909
}
868910

911+
if (ASI->getFunction()->hasOwnership()) {
912+
for (auto *user : UsersToRemove) {
913+
auto *store = dyn_cast<StoreInst>(user);
914+
if (!store ||
915+
store->getOwnershipQualifier() == StoreOwnershipQualifier::Trivial)
916+
continue;
917+
// In ossa, we are going to delete the dead store and insert a
918+
// destroy_value of the store's source. This is shortening the store's
919+
// source lifetime. Check if there was a pointer escape of the store's
920+
// source, if so bail out.
921+
if (findPointerEscape(store->getSrc())) {
922+
return false;
923+
}
924+
}
925+
for (auto *user : UsersToRemove) {
926+
auto *store = dyn_cast<StoreInst>(user);
927+
if (!store ||
928+
store->getOwnershipQualifier() == StoreOwnershipQualifier::Trivial)
929+
continue;
930+
SILBuilderWithScope(store).createDestroyValue(store->getLoc(),
931+
store->getSrc());
932+
}
933+
}
934+
869935
// Remove the AllocRef and all of its users.
870936
removeInstructions(
871937
ArrayRef<SILInstruction*>(UsersToRemove.begin(), UsersToRemove.end()));
@@ -883,11 +949,38 @@ bool DeadObjectElimination::processKeyPath(KeyPathInst *KPI) {
883949
return false;
884950
}
885951

886-
// For simplicity just bail if the keypath has a non-trivial operands.
887-
// TODO: don't bail but insert compensating destroys for such operands.
952+
bool hasOwnership = KPI->getFunction()->hasOwnership();
888953
for (const Operand &Op : KPI->getPatternOperands()) {
889-
if (!Op.get()->getType().isTrivial(*KPI->getFunction()))
954+
// In non-ossa, bail out if we have non-trivial pattern operands.
955+
if (!hasOwnership) {
956+
if (Op.get()->getType().isTrivial(*KPI->getFunction()))
957+
return false;
958+
continue;
959+
}
960+
// In ossa, bail out if we have non-trivial pattern operand values that are
961+
// lexical.
962+
if (Op.get()->isLexical()) {
890963
return false;
964+
}
965+
}
966+
967+
if (KPI->getFunction()->hasOwnership()) {
968+
for (const Operand &Op : KPI->getPatternOperands()) {
969+
if (Op.get()->getType().isTrivial(*KPI->getFunction()))
970+
continue;
971+
// In ossa, we are going to delete the dead keypath which was consuming
972+
// the pattern operand and insert a destroy_value of the pattern operand
973+
// value. This is shortening the pattern operand value's lifetime. Check
974+
// if there was a pointer escape, if so bail out.
975+
if (findPointerEscape(Op.get())) {
976+
return false;
977+
}
978+
}
979+
for (const Operand &Op : KPI->getPatternOperands()) {
980+
if (Op.get()->getType().isTrivial(*KPI->getFunction()))
981+
continue;
982+
SILBuilderWithScope(KPI).createDestroyValue(KPI->getLoc(), Op.get());
983+
}
891984
}
892985

893986
// Remove the keypath and all of its users.
@@ -924,10 +1017,11 @@ bool DeadObjectElimination::getDeadInstsAfterInitializerRemoved(
9241017

9251018
if (auto *ARI = dyn_cast<AllocRefInstBase>(Arg0)) {
9261019
if (all_of(ARI->getUses(), [&](Operand *Op) -> bool {
927-
if (Op->getUser() == AI)
1020+
auto *user = Op->getUser();
1021+
if (user == AI)
9281022
return true;
929-
if (auto *SRI = dyn_cast<StrongReleaseInst>(Op->getUser())) {
930-
ToDestroy.emplace_back(SRI);
1023+
if (isa<StrongReleaseInst>(user) || isa<DestroyValueInst>(user)) {
1024+
ToDestroy.emplace_back(user);
9311025
return true;
9321026
}
9331027
return false;
@@ -955,19 +1049,33 @@ bool DeadObjectElimination::getDeadInstsAfterInitializerRemoved(
9551049
// or we could also handle calls to array.init.
9561050
bool DeadObjectElimination::removeAndReleaseArray(
9571051
SingleValueInstruction *NewArrayValue, DeadEndBlocks &DEBlocks) {
958-
TupleExtractInst *ArrayDef = nullptr;
959-
TupleExtractInst *StorageAddress = nullptr;
960-
for (auto *Op : NewArrayValue->getUses()) {
961-
auto *TupleElt = dyn_cast<TupleExtractInst>(Op->getUser());
962-
if (!TupleElt)
1052+
SILValue ArrayDef = nullptr;
1053+
SILValue StorageAddress = nullptr;
1054+
1055+
if (NewArrayValue->getFunction()->hasOwnership()) {
1056+
auto *destructureTuple =
1057+
NewArrayValue->getSingleConsumingUserOfType<DestructureTupleInst>();
1058+
if (!destructureTuple) {
9631059
return false;
964-
if (TupleElt->getFieldIndex() == 0 && !ArrayDef) {
965-
ArrayDef = TupleElt;
966-
} else if (TupleElt->getFieldIndex() == 1 && !StorageAddress) {
967-
StorageAddress = TupleElt;
968-
} else {
1060+
}
1061+
if (destructureTuple->getNumResults() != 2) {
9691062
return false;
9701063
}
1064+
ArrayDef = destructureTuple->getResult(0);
1065+
StorageAddress = destructureTuple->getResult(1);
1066+
} else {
1067+
for (auto *Op : NewArrayValue->getUses()) {
1068+
auto *TupleElt = dyn_cast<TupleExtractInst>(Op->getUser());
1069+
if (!TupleElt)
1070+
return false;
1071+
if (TupleElt->getFieldIndex() == 0 && !ArrayDef) {
1072+
ArrayDef = TupleElt;
1073+
} else if (TupleElt->getFieldIndex() == 1 && !StorageAddress) {
1074+
StorageAddress = TupleElt;
1075+
} else {
1076+
return false;
1077+
}
1078+
}
9711079
}
9721080
if (!ArrayDef)
9731081
return false; // No Array object to delete.

0 commit comments

Comments
 (0)