mirror of
https://github.com/apple/swift.git
synced 2026-10-04 11:58:22 +02:00
With incomplete lifetimes, a value may have an "incomplete lifetime"--it
may not be destroyed on paths into dead-end regions. When a value lacks
a destroy on a path into a dead-end region, it is effectively destroyed
at its availability boundary (e.g. an `unreachable` instruction).
Indeed, lifetime completion amounts to making such implicit destroys
explicit (with correct nesting).
`SemanticARCOpts`' `performGuaranteedCopyValueOptimization` attempts to
eliminate a `copy_value` of a guaranteed value. To do so, it computes
the live range (`OwnershipLiveRange`) of the copy_value. Among other
things, it checks that all destroys of the copy_value are within the
scope of all guaranteed bases of the copied value. If any destroys are
_not_ within any of those scopes, the copy cannot be eliminated (because
the copy would have been originally live beyond the range which it would
be live in after copy elimination).
A value with an incomplete lifetime is implicitly destroyed at its
availability boundary. So those implicit destroys along the
availability boundary must _also_ be within the scopes of those
guaranteed bases.
Previously, implicit destroys along availability boundaries were not
considered. This resulted in invalid shortening of lifetimes--before
the transformation a value would be unconsumed up to its availability
boundary; after the transformation it would be consumed somewhere before
that. For example:
```
sil [ossa] @f : $@convention(thin) (@owned Outer) -> () {
entry(%o : $*Outer):
%o_borrow = begin_borrow %o : $Outer
%i = struct_extract %o_borrow : $Outer, #Outer.inner
%i_copy = copy_value %i : $Inner
end_borrow %o_borrow : $Outer
destroy_value %o : $Outer
apply @g(%i_copy) : $@convention(thin) (@guaranteed Inner) -> ()
unreachable // %i_copy implicitly destroyed
}
```
Here, `%i_copy` is implicitly destroyed at `unreachable` but `%i` is
consumed at the scope end of `%o_borrow`. That means that an attempt to
RAUW `%i_copy` with `%i` would be invalid because uses of `%i_copy` may
be outside the live range of `%i`.
(Note that this happens not to occur in the above example because
there's a bailout in the case that there are no destroys, but perturbing
the example to include a cond_br on one branch of which the value is
destroyed results in the miscompile described above.)
Here, this is fixed by adding the instructions on the availability
boundary at which the value is implicitly destroyed to the live range.
Do this by adding the instructions on the availability boundary of each
def from which the live range is generated to the live range.
One wrinkle is that the OwnershipLiveRange utility is used in one place
by a utility when SIL is in an invalid state (phi uses have been
replaced with undef so values leaks on those paths). Account for this
by manually fixing up the liveness instance passed to the lifetime
completion utility.
rdar://157291161
198 lines
7.8 KiB
C++
198 lines
7.8 KiB
C++
//===--- OwnershipLiveRange.h ---------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SWIFT_SILOPTIMIZER_SEMANTICARC_OWNERSHIPLIVERANGE_H
|
|
#define SWIFT_SILOPTIMIZER_SEMANTICARC_OWNERSHIPLIVERANGE_H
|
|
|
|
#include "swift/SIL/OwnershipUtils.h"
|
|
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
|
|
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
|
|
|
|
namespace swift {
|
|
namespace semanticarc {
|
|
|
|
/// This class represents an "extended live range" of an owned value. Such a
|
|
/// representation includes:
|
|
///
|
|
/// 1. The owned introducing value.
|
|
/// 2. Any forwarding instructions that consume the introduced value
|
|
/// (transitively) and then propagate a new owned value.
|
|
/// 3. Transitive destroys on the forwarding instructions/destroys on the owned
|
|
/// introducing value.
|
|
/// 4. Any unknown consuming uses that are not understood by this code.
|
|
///
|
|
/// This allows for this to be used to convert such a set of uses from using
|
|
/// owned ownership to using guaranteed ownership by converting the
|
|
/// destroy_value -> end_borrow and "flipping" the ownership of individual
|
|
/// forwarding instructions.
|
|
///
|
|
/// NOTE: We do not look through "phi nodes" in the ownership graph (e.x.: real
|
|
/// phi arguments, struct, tuple). Instead we represent those as nodes in a
|
|
/// larger phi ownership web, connected via individual OwnershipLiveRange.
|
|
class LLVM_LIBRARY_VISIBILITY OwnershipLiveRange {
|
|
/// The value that we are computing the LiveRange for. Expected to be an owned
|
|
/// introducer and not to be forwarding.
|
|
OwnedValueIntroducer introducer;
|
|
|
|
/// A vector that we store all of our uses into.
|
|
///
|
|
/// Some properties of this array are:
|
|
///
|
|
/// 1. It is only mutated in the constructor of LiveRange.
|
|
///
|
|
/// 2. destroyingUses, ownershipForwardingUses, and unknownConsumingUses are
|
|
/// views into this array. We store the respective uses in the aforementioned
|
|
/// order. This is why it is important not to mutate consumingUses after we
|
|
/// construct the LiveRange since going from small -> large could invalidate
|
|
/// the uses.
|
|
///
|
|
/// These UsePoints may be Operands or SILInstructions (terminators of blocks
|
|
/// in dead-end regions along the value's availability boundary).
|
|
SmallVector<UsePoint, 6> consumingUses;
|
|
|
|
/// A list of destroy_values of the live range.
|
|
///
|
|
/// This is just a view into consumingUses.
|
|
///
|
|
/// These UsePoints may be Operands or SILInstructions (terminators of blocks
|
|
/// in dead-end regions along the value's availability boundary).
|
|
ArrayRef<UsePoint> destroyingUses;
|
|
|
|
/// A list of forwarding instructions that forward owned ownership, but that
|
|
/// are also able to be converted to guaranteed ownership.
|
|
///
|
|
/// If we are able to eliminate this LiveRange due to it being from a
|
|
/// guaranteed value, we must flip the ownership of all of these instructions
|
|
/// to guaranteed from owned.
|
|
///
|
|
/// NOTE: Normally only destroying or consuming uses end the live range. We
|
|
/// copy these transitive uses as well into the consumingUses array since
|
|
/// transitive uses can extend a live range up to an unreachable block without
|
|
/// ultimately being consuming. In such a situation if we did not also store
|
|
/// this into consuming uses, we would not be able to ascertain just using the
|
|
/// "consumingUses" array the true lifetime of the OwnershipLiveRange.
|
|
///
|
|
/// Corresponds to isOwnershipForwardingInst(...).
|
|
///
|
|
/// These UsePoints must all be Operands.
|
|
ArrayRef<UsePoint> ownershipForwardingUses;
|
|
|
|
/// Consuming uses that we were not able to understand as a forwarding
|
|
/// instruction or a destroy_value. These must be passed a strongly control
|
|
/// equivalent +1 value.
|
|
///
|
|
/// These UsePoints must all be Operands.
|
|
ArrayRef<UsePoint> unknownConsumingUses;
|
|
|
|
public:
|
|
OwnershipLiveRange(SILValue value,
|
|
ArrayRef<std::pair<Operand *, SILValue>> extraUses = {});
|
|
OwnershipLiveRange(const OwnershipLiveRange &) = delete;
|
|
OwnershipLiveRange &operator=(const OwnershipLiveRange &) = delete;
|
|
|
|
enum class HasConsumingUse_t {
|
|
No = 0,
|
|
YesButAllPhiArgs = 1,
|
|
Yes = 2,
|
|
};
|
|
|
|
/// Return true if v only has invalidating uses that are destroy_value. Such
|
|
/// an owned value is said to represent a dead "live range".
|
|
///
|
|
/// Semantically this implies that a value is never passed off as +1 to memory
|
|
/// or another function implying it can be used everywhere at +0.
|
|
HasConsumingUse_t
|
|
hasUnknownConsumingUse(bool assumingFixedPoint = false) const;
|
|
|
|
UsePointInstructionRange getDestroyingInsts() const {
|
|
return UsePointInstructionRange(destroyingUses, UsePointToInstruction());
|
|
}
|
|
|
|
UsePointInstructionRange getUnknownConsumingInsts() const {
|
|
return UsePointInstructionRange(unknownConsumingUses,
|
|
UsePointToInstruction());
|
|
}
|
|
|
|
UsePointInstructionRange getAllConsumingInsts() const {
|
|
return UsePointInstructionRange(consumingUses, UsePointToInstruction());
|
|
}
|
|
|
|
/// If this LiveRange has a single unknown destroying use, return that
|
|
/// use. Otherwise, return nullptr.
|
|
Operand *getSingleUnknownConsumingUse() const {
|
|
if (unknownConsumingUses.size() != 1) {
|
|
return nullptr;
|
|
}
|
|
auto point = unknownConsumingUses.front();
|
|
return point.getOperand();
|
|
}
|
|
|
|
OwnedValueIntroducer getIntroducer() const { return introducer; }
|
|
|
|
PointOperandRange getOwnershipForwardingUses() const {
|
|
return PointOperandRange(ownershipForwardingUses, PointToOperand());
|
|
}
|
|
|
|
void convertOwnedGeneralForwardingUsesToGuaranteed() &&;
|
|
|
|
/// A consuming operation that:
|
|
///
|
|
/// 1. If \p insertEndBorrows is true inserts end borrows at all
|
|
/// destroying insts locations.
|
|
///
|
|
/// 2. Deletes all destroy_values.
|
|
///
|
|
/// 3. RAUW value with newGuaranteedValue.
|
|
///
|
|
/// 4. Convert all of the general forwarding instructions from
|
|
/// @owned -> @guaranteed. "Like Dominoes".
|
|
///
|
|
/// 5. Leaves all of the unknown consuming users alone. It is up to
|
|
/// the caller to handle converting their ownership.
|
|
void convertToGuaranteedAndRAUW(SILValue newGuaranteedValue,
|
|
InstModCallbacks callbacks) &&;
|
|
|
|
/// A consuming operation that in order:
|
|
///
|
|
/// 1. Converts the phi argument to be guaranteed via setOwnership.
|
|
///
|
|
/// 2. If this consumes a borrow, insert end_borrows at the relevant
|
|
/// destroy_values.
|
|
///
|
|
/// 3. Deletes all destroy_values.
|
|
///
|
|
/// 4. Converts all of the general forwarding instructions from @owned ->
|
|
/// @guaranteed. "Like Dominoes".
|
|
///
|
|
/// NOTE: This leaves all of the unknown consuming users alone. It is up to
|
|
/// the caller to handle converting their ownership.
|
|
///
|
|
/// NOTE: This routine leaves inserting begin_borrows for the incoming values
|
|
/// to the caller since those are not part of the LiveRange itself.
|
|
void convertJoinedLiveRangePhiToGuaranteed(
|
|
DeadEndBlocks &deadEndBlocks, ValueLifetimeAnalysis::Frontier &scratch,
|
|
InstModCallbacks callbacks) &&;
|
|
|
|
/// Given a new guaranteed value, insert end_borrow for the newGuaranteedValue
|
|
/// at all of our destroy_values in preparation for converting from owned to
|
|
/// guaranteed.
|
|
///
|
|
/// This is used when converting load [copy] -> load_borrow.
|
|
void insertEndBorrowsAtDestroys(SILValue newGuaranteedValue,
|
|
DeadEndBlocks &deadEndBlocks,
|
|
ValueLifetimeAnalysis::Frontier &scratch);
|
|
};
|
|
} // namespace semanticarc
|
|
} // namespace swift
|
|
|
|
#endif // SWIFT_SILOPTIMIZER_SEMANTICARC_OWNERSHIPLIVERANGE_H
|