Skip to content

Commit 926f105

Browse files
authored
Merge pull request #30087 from gottesmm/pr-7d5f0d45e4677ebbd92aeb1e261ab388a4ca5756
[ownership] Add simple support for concatenating together simple live ranges
2 parents 4fd17c1 + bdf6143 commit 926f105

File tree

5 files changed

+438
-7
lines changed

5 files changed

+438
-7
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,10 @@ bool isGuaranteedForwardingValue(SILValue value);
218218
/// forward guaranteed ownership.
219219
bool isOwnedForwardingValueKind(SILNodeKind kind);
220220

221+
/// Does this SILInstruction 'forward' owned ownership, but may not be able to
222+
/// forward guaranteed ownership.
223+
bool isOwnedForwardingInstruction(SILInstruction *inst);
224+
221225
struct BorrowScopeOperandKind {
222226
enum Kind {
223227
BeginBorrow,

include/swift/SIL/SILValue.h

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,22 @@ class ValueBase : public SILNode, public SILAllocated<ValueBase> {
272272
/// otherwise.
273273
inline Operand *getSingleUse() const;
274274

275+
/// Returns .some(single user) if this value is non-trivial, we are in ossa,
276+
/// and it has a single consuming user. Returns .none otherwise.
277+
inline Operand *getSingleConsumingUse() const;
278+
275279
template <class T>
276280
inline T *getSingleUserOfType() const;
277281

282+
template <class T> inline T *getSingleConsumingUserOfType() const;
283+
284+
/// Returns true if this operand has exactly two.
285+
///
286+
/// This is useful if one has found a predefined set of 2 unique users and
287+
/// wants to check if there are any other users without iterating over the
288+
/// entire use list.
289+
inline bool hasTwoUses() const;
290+
278291
/// Helper struct for DowncastUserFilterRange
279292
struct UseToUser;
280293

@@ -647,6 +660,10 @@ class Operand {
647660
/// Returns true if this operand acts as a use that consumes its associated
648661
/// value.
649662
bool isConsumingUse() const {
663+
// Type dependent uses can never be consuming and do not have valid
664+
// ownership maps since they do not participate in the ownership system.
665+
if (isTypeDependent())
666+
return false;
650667
auto map = getOwnershipKindMap();
651668
auto constraint = map.getLifetimeConstraint(get().getOwnershipKind());
652669
return constraint == UseLifetimeConstraint::MustBeInvalidated;
@@ -748,17 +765,48 @@ inline Operand *ValueBase::getSingleUse() const {
748765
return Op;
749766
}
750767

768+
inline Operand *ValueBase::getSingleConsumingUse() const {
769+
Operand *result = nullptr;
770+
for (auto *op : getUses()) {
771+
if (op->isConsumingUse()) {
772+
if (result) {
773+
return nullptr;
774+
}
775+
result = op;
776+
}
777+
}
778+
return result;
779+
}
780+
781+
inline bool ValueBase::hasTwoUses() const {
782+
auto iter = use_begin(), end = use_end();
783+
for (unsigned i = 0; i < 2; ++i) {
784+
if (iter == end)
785+
return false;
786+
++iter;
787+
}
788+
return iter == end;
789+
}
790+
751791
template <class T>
752792
inline T *ValueBase::getSingleUserOfType() const {
753-
T *Result = nullptr;
754-
for (auto *Op : getUses()) {
755-
if (auto *Tmp = dyn_cast<T>(Op->getUser())) {
756-
if (Result)
793+
T *result = nullptr;
794+
for (auto *op : getUses()) {
795+
if (auto *tmp = dyn_cast<T>(op->getUser())) {
796+
if (result)
757797
return nullptr;
758-
Result = Tmp;
798+
result = tmp;
759799
}
760800
}
761-
return Result;
801+
return result;
802+
}
803+
804+
template <class T> inline T *ValueBase::getSingleConsumingUserOfType() const {
805+
auto *op = getSingleConsumingUse();
806+
if (!op)
807+
return nullptr;
808+
809+
return dyn_cast<T>(op->getUser());
762810
}
763811

764812
struct ValueBase::UseToUser {

lib/SIL/OwnershipUtils.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ bool swift::isOwnedForwardingValueKind(SILNodeKind kind) {
7373
}
7474
}
7575

76+
bool swift::isOwnedForwardingInstruction(SILInstruction *inst) {
77+
auto kind = inst->getKind();
78+
switch (kind) {
79+
case SILInstructionKind::BranchInst:
80+
return true;
81+
default:
82+
return isOwnershipForwardingValueKind(SILNodeKind(kind));
83+
}
84+
}
85+
7686
bool swift::isGuaranteedForwardingValue(SILValue value) {
7787
// If we have an argument from a transforming terminator, we can forward
7888
// guaranteed.

lib/SILOptimizer/Transforms/SemanticARCOpts.cpp

Lines changed: 172 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ class LiveRange {
5959

6060
public:
6161
LiveRange(SILValue value);
62-
6362
LiveRange(const LiveRange &) = delete;
6463
LiveRange &operator=(const LiveRange &) = delete;
6564

@@ -454,6 +453,7 @@ struct SemanticARCOptVisitor
454453

455454
bool performGuaranteedCopyValueOptimization(CopyValueInst *cvi);
456455
bool eliminateDeadLiveRangeCopyValue(CopyValueInst *cvi);
456+
bool tryJoiningCopyValueLiveRangeWithOperand(CopyValueInst *cvi);
457457
};
458458

459459
} // end anonymous namespace
@@ -824,13 +824,184 @@ bool SemanticARCOptVisitor::eliminateDeadLiveRangeCopyValue(CopyValueInst *cvi)
824824
return true;
825825
}
826826

827+
// Handle simple checking where we do not need to form live ranges and visit a
828+
// bunch of instructions.
829+
static bool canSafelyJoinSimpleRange(SILValue cviOperand,
830+
DestroyValueInst *cviOperandDestroy,
831+
CopyValueInst *cvi) {
832+
// We only handle cases where our copy_value has a single consuming use that
833+
// is not a forwarding use. We need to use the LiveRange functionality to
834+
// guarantee correctness in the presence of forwarding uses.
835+
//
836+
// NOTE: This use may be any type of consuming use and may not be a
837+
// destroy_value.
838+
auto *cviConsumer = cvi->getSingleConsumingUse();
839+
if (!cviConsumer || isOwnedForwardingInstruction(cviConsumer->getUser())) {
840+
return false;
841+
}
842+
843+
// Ok, we may be able to eliminate this. The main thing we need to be careful
844+
// of here is that if the destroy_value is /after/ the consuming use of the
845+
// operand of copy_value, we may have normal uses of the copy_value's operand
846+
// that would become use-after-frees since we would be shrinking the lifetime
847+
// of the object potentially. Consider the following SIL:
848+
//
849+
// %0 = ...
850+
// %1 = copy_value %0
851+
// apply %cviConsumer(%1)
852+
// apply %guaranteedUser(%0)
853+
// destroy_value %0
854+
//
855+
// Easily, if we were to eliminate the copy_value, destroy_value, the object's
856+
// lifetime could potentially be shrunk before guaranteedUser is executed,
857+
// causing guaranteedUser to be a use-after-free.
858+
//
859+
// As an extra wrinkle, until all interior pointer constructs (e.x.:
860+
// project_box) are guaranteed to be guaranted by a begin_borrow, we can not
861+
// in general safely shrink lifetimes. So even if we think we can prove that
862+
// all non-consuming uses of %0 are before apply %cviConsumer, we may miss
863+
// implicit uses that are not guarded yet by a begin_borrow, resulting in
864+
// use-after-frees.
865+
//
866+
// With that in mind, we only handle cases today where we can prove that
867+
// destroy_value is strictly before the consuming use of the operand. This
868+
// guarantees that we are not shrinking the lifetime of the underlying object.
869+
//
870+
// First we handle the simple case: where the cviConsumer is a return inst. In
871+
// such a case, we know for sure that cviConsumer post-dominates the
872+
// destroy_value.
873+
auto cviConsumerIter = cviConsumer->getUser()->getIterator();
874+
if (isa<ReturnInst>(cviConsumerIter)) {
875+
return true;
876+
}
877+
878+
// Then see if our cviConsumer is in the same block as a return inst and the
879+
// destroy_value is not. In that case, we know that the cviConsumer must
880+
// post-dominate the destroy_value.
881+
auto *cviConsumingBlock = cviConsumerIter->getParent();
882+
if (isa<ReturnInst>(cviConsumingBlock->getTerminator()) &&
883+
cviConsumingBlock != cviOperandDestroy->getParent()) {
884+
return true;
885+
}
886+
887+
// Otherwise, we only support joining live ranges where the cvi and the cvi's
888+
// operand's destroy are in the same block with the destroy_value of cvi
889+
// operand needing to be strictly after the copy_value. This analysis can be
890+
// made significantly stronger by using LiveRanges, but this is simple for
891+
// now.
892+
auto cviOperandDestroyIter = cviOperandDestroy->getIterator();
893+
if (cviConsumingBlock != cviOperandDestroyIter->getParent()) {
894+
return false;
895+
}
896+
897+
// TODO: This should really be llvm::find, but for some reason, the templates
898+
// do not match up given the current state of the iterators. This impl works
899+
// in a pinch though.
900+
return llvm::any_of(
901+
llvm::make_range(cviOperandDestroyIter,
902+
cviOperandDestroyIter->getParent()->end()),
903+
[&](const SILInstruction &val) { return &*cviConsumerIter == &val; });
904+
}
905+
906+
// # The Problem We Are Solving
907+
//
908+
// The main idea here is that we are trying to eliminate the simplest, easiest
909+
// form of live range joining. Consider the following SIL:
910+
//
911+
// ```
912+
// %cviOperand = ... // @owned value
913+
// %cvi = copy_value %cviOperand // copy of @owned value
914+
// ...
915+
// destroy_value %cviOperandDestroy // destruction of @owned value
916+
// ...
917+
// apply %consumingUser(%cvi) // destruction of copy of @owned value
918+
// ```
919+
//
920+
// We want to reduce reference count traffic by eliminating the middle
921+
// copy/destroy yielding:
922+
//
923+
// ```
924+
// %cviOperand = ... // @owned value
925+
// // *eliminated copy_value*
926+
// ...
927+
// // *eliminated destroy_value*
928+
// ...
929+
// apply %consumingUser(%cviOperand) // destruction of copy of @owned
930+
// value
931+
// ```
932+
//
933+
// # Safety
934+
//
935+
// In order to do this safely, we need to take the union of the two objects
936+
// lifetimes since we are only joining lifetimes. This ensures that we can rely
937+
// on SILGen's correctness on inserting safe lifetimes. To keep this simple
938+
// today we only optimize if the destroy_value and consuming user are in the
939+
// same block and the consuming user is later in the block than the
940+
// destroy_value.
941+
//
942+
// DISCUSSION: The reason why we do not shrink lifetimes today is that all
943+
// interior pointers (e.x. project_box) are properly guarded by
944+
// begin_borrow. Because of that we can not shrink lifetimes and instead rely on
945+
// SILGen's correctness.
946+
bool SemanticARCOptVisitor::tryJoiningCopyValueLiveRangeWithOperand(
947+
CopyValueInst *cvi) {
948+
// First do a quick check if our operand is owned. If it is not owned, we can
949+
// not join live ranges.
950+
SILValue operand = cvi->getOperand();
951+
if (operand.getOwnershipKind() != ValueOwnershipKind::Owned) {
952+
return false;
953+
}
954+
955+
// Then check if our operand has a single destroy_value. If it does and that
956+
// destroy_value is strictly before the consumer of our copy_value in the same
957+
// block as the consumer of said copy_value then we can always join the live
958+
// ranges.
959+
//
960+
// Example:
961+
//
962+
// ```
963+
// %1 = copy_value %0
964+
// ...
965+
// destroy_value %0
966+
// apply %consumingUser(%1)
967+
// ```
968+
// ->
969+
//
970+
// ```
971+
// apply %consumingUser(%0)
972+
// ```
973+
//
974+
// DISCUSSION: We need to ensure that the consuming use of the copy_value is
975+
// strictly after the destroy_value to ensure that we do not shrink the live
976+
// range of the operand if the operand has any normal uses beyond our copy
977+
// value. Otherwise, we could have normal uses /after/ the consuming use of
978+
// our copy_value.
979+
if (auto *dvi = operand->getSingleConsumingUserOfType<DestroyValueInst>()) {
980+
if (canSafelyJoinSimpleRange(operand, dvi, cvi)) {
981+
eraseInstruction(dvi);
982+
eraseAndRAUWSingleValueInstruction(cvi, operand);
983+
NumEliminatedInsts += 2;
984+
return true;
985+
}
986+
}
987+
988+
// Otherwise, we couldn't handle this case, so return false.
989+
return false;
990+
}
991+
827992
bool SemanticARCOptVisitor::visitCopyValueInst(CopyValueInst *cvi) {
828993
// If our copy value inst has only destroy_value users, it is a dead live
829994
// range. Try to eliminate them.
830995
if (eliminateDeadLiveRangeCopyValue(cvi)) {
831996
return true;
832997
}
833998

999+
// Then see if copy_value operand's lifetime ends after our copy_value via a
1000+
// destroy_value. If so, we can join their lifetimes.
1001+
if (tryJoiningCopyValueLiveRangeWithOperand(cvi)) {
1002+
return true;
1003+
}
1004+
8341005
// Then try to perform the guaranteed copy value optimization.
8351006
if (performGuaranteedCopyValueOptimization(cvi)) {
8361007
return true;

0 commit comments

Comments
 (0)