mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
825 lines
28 KiB
C++
825 lines
28 KiB
C++
//===- LexicalDestroyFolding.cpp - Fold destroys into final owned applies -===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2022 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
/// After ShrinkBorrowScope and OSSACanonicalizeOwned both run, when a final
|
|
/// use of the extended simple lifetime of a begin_borrow [lexical] is as an
|
|
/// owned argument, we will have the following pattern:
|
|
///
|
|
/// %result = apply %fn(..., %copy, ...) : $... (..., @owned Ty, ...)
|
|
/// end_borrow %lifetime : $Ty
|
|
/// destroy_value %dvi
|
|
///
|
|
/// where %lifetime is the result of our begin_borrow [lexical]:
|
|
///
|
|
/// %lifetime = begin_borrow [lexical] %borrowee : $Ty
|
|
///
|
|
/// and %copy is a (transitive) copy of it
|
|
///
|
|
/// %copy = copy_value (copy_value (... %lifetime))
|
|
///
|
|
/// At that point, we want to fold the destroy_value, apply, and borrow into
|
|
///
|
|
/// end_borrow %lifetime : $Ty
|
|
/// apply %fn(..., %move, ...) : $... (..., @owned Ty, ...)
|
|
///
|
|
/// where %move is a the result of a new instruction added above our scope:
|
|
///
|
|
/// %move = move_value [lexical] %borrowee : $Ty
|
|
/// %lifetime = begin_borrow %move : $Ty
|
|
///
|
|
/// We only want to do this when it would mean allowing the callee to end a
|
|
/// lifetime of a value that the caller owned--if we can transfer ownership from
|
|
/// the caller to the callee. In order to transfer ownership, the caller must
|
|
/// first own the value, so we are only interested in borrows of owned values:
|
|
///
|
|
/// %borrowee : @owned $Ty
|
|
/// %lifetime = begin_borrow [lexical] %borrowee
|
|
///
|
|
/// At the other end, we can only transfer ownership if there are no more
|
|
/// interesting uses of the owned value after the apply. Specifically, we
|
|
/// require that the instruction after the end_borrow be a destroy_value of the
|
|
/// borrowee.
|
|
///
|
|
/// The simplest example:
|
|
///
|
|
/// %copy = copy_value %lifetime : $Ty
|
|
/// %result = apply %fn(..., %copy, ...) : $... (..., @owned Ty, ...)
|
|
/// end_borrow %lifetime : $Ty
|
|
/// destroy_value %borrowee : $Ty
|
|
///
|
|
/// Taken together, we get the simplest example of this transformation:
|
|
///
|
|
/// INPUT:
|
|
///
|
|
/// %borrowee : @owned
|
|
/// %lifetime = begin_borrow [lexical] %owned
|
|
/// %copy = copy_value %lifetime
|
|
/// apply %fn(%copy) : $... (@owned)
|
|
/// end_borrow %lifetime
|
|
/// destroy_value %borrowee
|
|
///
|
|
/// OUTPUT:
|
|
///
|
|
/// %borrowee : @owned
|
|
/// %move = move_value [lexical] %borrowee
|
|
/// %lifetime = begin_borrow [lexical] %move
|
|
/// end_borrow %lifetime
|
|
/// apply %fn(%move) : $... (@owned)
|
|
///
|
|
/// TODO: Handle partial_apply, try_apply, and begin_apply.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/SIL/BasicBlockDatastructures.h"
|
|
#include "swift/SIL/BasicBlockUtils.h"
|
|
#include "swift/SIL/OwnershipUtils.h"
|
|
#include "swift/SIL/PrunedLiveness.h"
|
|
#include "swift/SIL/SILBasicBlock.h"
|
|
#include "swift/SIL/SILBuilder.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/SILNode.h"
|
|
#include "swift/SIL/Test.h"
|
|
#include "swift/SILOptimizer/Analysis/Reachability.h"
|
|
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
|
#include "swift/SILOptimizer/Utils/InstructionDeleter.h"
|
|
#include "swift/SILOptimizer/Utils/OSSACanonicalizeGuaranteed.h"
|
|
#include "swift/SILOptimizer/Utils/OSSACanonicalizeOwned.h"
|
|
#include "swift/SILOptimizer/Utils/SILSSAUpdater.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
|
|
#define DEBUG_TYPE "copy-propagation"
|
|
|
|
using namespace swift;
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: LexicalDestroyFolding
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace LexicalDestroyFolding {
|
|
|
|
/// The environment within which to fold.
|
|
struct Context final {
|
|
/// The instruction that begins the borrow scope.
|
|
BeginBorrowInst *const introducer;
|
|
|
|
/// The function containing the introducer.
|
|
///
|
|
/// introducer->getFunction()
|
|
SILFunction *const function;
|
|
|
|
/// BorrowedValue(introducer)
|
|
BorrowedValue const borrowedValue;
|
|
|
|
/// The value whose lifetime is guaranteed by the lexical borrow scope.
|
|
///
|
|
/// introducer->getOperand()
|
|
SILValue const borrowee;
|
|
|
|
DominanceInfo &dominanceTree;
|
|
|
|
InstructionDeleter &deleter;
|
|
|
|
public:
|
|
Context(BeginBorrowInst *introducer, DominanceInfo &dominanceTree,
|
|
InstructionDeleter &deleter)
|
|
: introducer(introducer), function(introducer->getFunction()),
|
|
borrowedValue(BorrowedValue(introducer)),
|
|
borrowee(introducer->getOperand()), dominanceTree(dominanceTree),
|
|
deleter(deleter) {
|
|
assert(introducer->isLexical());
|
|
assert(introducer->getOperand()->getOwnershipKind() ==
|
|
OwnershipKind::Owned);
|
|
assert(borrowedValue.isLocalScope());
|
|
}
|
|
};
|
|
|
|
/// Fold within the specified context.
|
|
MoveValueInst *run(Context &);
|
|
|
|
/// The consuming use pattern we are trying to match and transform.
|
|
struct Match final {
|
|
ApplyInst *ai = nullptr;
|
|
EndBorrowInst *ebi = nullptr;
|
|
DestroyValueInst *dvi = nullptr;
|
|
|
|
/// Whether the match is a candidate for folding.
|
|
///
|
|
/// A partial match--which has both the end_borrow and the destroy value
|
|
/// but no apply--cannot itself be folded but is not an obstruction to
|
|
/// folding other candidates.
|
|
bool isFullMatch() {
|
|
assert(ebi != nullptr);
|
|
assert(dvi != nullptr);
|
|
return ai != nullptr;
|
|
}
|
|
};
|
|
|
|
/// A sequence of instructions under consideration for folding
|
|
struct Candidate final {
|
|
/// The instruction sequence itself.
|
|
Match match;
|
|
/// Whether the candidate could indeed be folded as determined by
|
|
/// isViableMatch.
|
|
bool viable;
|
|
/// The indices of the arguments of the apply to rewrite as determined by
|
|
/// rewritableArgumentIndicesForApply.
|
|
SmallVector<int, 2> argumentIndices;
|
|
};
|
|
|
|
/// The degree to which a match is a candidate for folding.
|
|
enum class MatchViability {
|
|
/// This match can be folded, supposing any can.
|
|
Viable,
|
|
/// This match cannot be folded, even if others can.
|
|
Nonviable,
|
|
/// Neither this match nor any other can be folded.
|
|
Illegal
|
|
};
|
|
|
|
struct Candidates final {
|
|
/// The sequences of scope ending instructions that are under consideration
|
|
/// for folding.
|
|
llvm::SmallVector<Candidate, 4> vector;
|
|
};
|
|
|
|
/// Quickly filter scope ends of %lifetime that MIGHT BE foldable.
|
|
class FindCandidates final {
|
|
|
|
Context const &context;
|
|
|
|
public:
|
|
FindCandidates(Context const &context) : context(context) {}
|
|
|
|
/// Find among the scope ending instructions of %lifetime any that match the
|
|
/// expected instruction sequence pattern.
|
|
///
|
|
/// Uses definesMatchingInstructionSequence to determine whether an
|
|
/// instruction is a candidate for folding.
|
|
///
|
|
/// Includes both full matches which could potentially be folded and partial
|
|
/// matches which need to be verified later aren't illegal in isViableMatch.
|
|
///
|
|
/// returns true if any full matches are found
|
|
/// false otherwise
|
|
bool run(Candidates &);
|
|
|
|
private:
|
|
/// Fast check for whether the given instruction is a candidate for folding.
|
|
///
|
|
/// Tries to find patterns like
|
|
///
|
|
/// apply
|
|
/// end_borrow
|
|
/// ... // instructions which OSSACanonicalizeOwned will not
|
|
/// // hoist destroys over
|
|
/// destroy_value %borrowee
|
|
///
|
|
/// If None is returned, we can't do the transformation at any end_borrow.
|
|
/// If a Match WITHOUT an apply is returned, we can't do the transformation
|
|
/// on the provided instruction but we still might be able to do it on the
|
|
/// other scope ending instructions.
|
|
/// If a Match WITH an apply is returned, we might be able to transform this
|
|
/// instruction, so more expensive checks are in order.
|
|
std::optional<Match>
|
|
definesMatchingInstructionSequence(SILInstruction *) const;
|
|
|
|
/// Whether the specified instruction is or might be the beginning of a
|
|
/// sequence of "inconsequential" instructions the last of which destroys
|
|
/// %borrowee.
|
|
///
|
|
/// OSSACanonicalizeOwned will put destroy_value instructions after every
|
|
/// final non-consuming use of %borrowee. So it will put a
|
|
/// destroy_value after an
|
|
/// end_borrow %lifetime
|
|
/// if there are no subsequent uses of %borrowee. However, if there
|
|
/// is already one or more other instructions whose opcodes satisfy
|
|
/// OSSACanonicalizeOwned::ignoredByDestroyHoisting just after
|
|
/// end_borrow %lifetime,
|
|
/// it won't hoist the
|
|
/// destroy_value %borrowee
|
|
/// over them.
|
|
///
|
|
/// Consequently, it's not good enough just to look at the instruction
|
|
/// immediately following
|
|
/// end_borrow %lifetime
|
|
/// Instead, we need to look over the sequence of them.
|
|
///
|
|
/// To be certain that the sequence is valid, we need to check that none of
|
|
/// the instructions between the end_borrow and the destroy_value are users of
|
|
/// %borrowee. That requires having a set of uses to check for membership
|
|
/// in, though, so that is postponed until isViableMatch.
|
|
///
|
|
/// returns the destroy_value instruction in the sequence that destroys
|
|
/// %borrowee or nullptr if there isn't one
|
|
DestroyValueInst *findNextBorroweeDestroy(SILInstruction *) const;
|
|
};
|
|
|
|
/// How %lifetime is used.
|
|
struct IntroducerUsage final {
|
|
/// The operands that are uses of introducer.
|
|
SmallPtrSet<Operand *, 16> uses;
|
|
/// The instructions that are users of the simple extended borrow scope.
|
|
SmallPtrSet<SILInstruction *, 16> users;
|
|
};
|
|
|
|
/// Identifies all the simple extended users of %lifetime.
|
|
///
|
|
/// returns true if none of the uses of %lifetime escaped
|
|
/// false otherwise
|
|
bool findIntroducerUsage(Context const &, IntroducerUsage &);
|
|
|
|
/// How %borrowee is used.
|
|
struct BorroweeUsage final {
|
|
/// The operands that are uses of the borrowee.
|
|
SmallVector<Operand *, 16> uses;
|
|
/// The instructions that are users of the borrowee.
|
|
///
|
|
/// A set of the users of uses for fast membership checking.
|
|
SmallPtrSet<SILInstruction *, 16> users;
|
|
};
|
|
|
|
/// Find all uses of %borrowee that are dominated by introducer.
|
|
///
|
|
/// We are only interested in those dominated by introducer because those are
|
|
/// the uses all of which must be "outside" the liveness boundary of
|
|
/// %lifetime. In detail, PrunedLiveness::isWithinBoundary relies on
|
|
/// clients to know that instructions are after the start of liveness. We
|
|
/// determine this via the dominance tree.
|
|
bool findBorroweeUsage(Context const &, BorroweeUsage &);
|
|
|
|
/// Sift scope ends of %lifetime for those that CAN be folded.
|
|
class FilterCandidates final {
|
|
|
|
Context const &context;
|
|
IntroducerUsage const &introducerUsage;
|
|
BorroweeUsage const &borroweeUsage;
|
|
|
|
public:
|
|
FilterCandidates(Context const &context,
|
|
IntroducerUsage const &introducerUsage,
|
|
BorroweeUsage const &borroweeUsage)
|
|
: context(context), introducerUsage(introducerUsage),
|
|
borroweeUsage(borroweeUsage){};
|
|
|
|
/// Determines whether each candidate is viable for folding.
|
|
///
|
|
/// returns true if any candidates were viable
|
|
/// false otherwise
|
|
bool run(Candidates &candidates);
|
|
|
|
private:
|
|
/// Slow check, dependent on finding users, that a Match found by
|
|
/// definesMatchingInstructionSequence can be folded.
|
|
MatchViability isViableMatch(Match &, SmallVectorImpl<int> &) const;
|
|
|
|
/// Find the arguments in the specified apply that could be rewritten.
|
|
bool rewritableArgumentIndicesForApply(ApplySite,
|
|
SmallVectorImpl<int> &indices) const;
|
|
|
|
/// Whether the specified value is %lifetime or its iterated copy_value.
|
|
///
|
|
/// In other words, it has to be a simple extended def of %lifetime.
|
|
bool isSimpleExtendedIntroducerDef(SILValue value) const;
|
|
};
|
|
|
|
/// Whether there are any uses of the borrowee within the borrow scope.
|
|
///
|
|
/// If there are, we can't fold the apply. Specifically, we can't introduce
|
|
/// a move_value [lexical] %borrowee because that value still needs to be used
|
|
/// in those locations.
|
|
///
|
|
/// For example, given the following SIL
|
|
/// %borrowee : @owned
|
|
/// %lifetime = begin_borrow [lexical] %borrowee
|
|
/// apply %take_guaranteed(%borrowee)
|
|
/// %copy = copy_value %lifetime
|
|
/// apply %take_owned(%copy)
|
|
/// end_borrow %lifetime
|
|
/// destroy_value %borrowee
|
|
/// we can't rewrite like
|
|
/// %borrowee : @owned
|
|
/// %move = move_value [lexical] %borrowee
|
|
/// %lifetime = begin_borrow [lexical] %move
|
|
/// apply %take_guaranteed(??????)
|
|
/// apply %take_owned(%move)
|
|
/// because there is no appropriate value to pass to %take_guaranteed.
|
|
/// Specifically, it's not legal to use %move there because that would make
|
|
/// the instruction a user of the lexical scope which it was not before.
|
|
bool borroweeHasUsesWithinBorrowScope(Context const &, BorroweeUsage const &);
|
|
|
|
/// Rewrite the appropriate scope ends of %lifetime.
|
|
class Rewriter final {
|
|
|
|
Context &context;
|
|
Candidates const &candidates;
|
|
|
|
// The move_value [lexical] instruction that was added during the run.
|
|
//
|
|
// Defined during createMove.
|
|
MoveValueInst *mvi = nullptr;
|
|
|
|
public:
|
|
Rewriter(Context &context, Candidates const &candidates)
|
|
: context(context), candidates(candidates){};
|
|
|
|
/// Make all changes required to fold the viable candidates.
|
|
///
|
|
/// Specifically:
|
|
/// - create a single move_value [lexical]
|
|
/// %move = move_value [lexical] %borrowee
|
|
/// %lifetime = begin_borrow [lexical] %move
|
|
/// - transform all the candidates from
|
|
/// apply %fn(..., %copy, ...)
|
|
/// end_borrow %lifetime
|
|
/// ...
|
|
/// destroy_value %borrowee
|
|
/// to
|
|
/// end_borrow %lifetime
|
|
/// apply %fn(..., %move, ...)
|
|
/// ...
|
|
/// - update SSA now that a second def has been introduced for
|
|
/// %borrowee
|
|
MoveValueInst *run();
|
|
|
|
private:
|
|
/// Add the new move_value [lexical] above the begin_borrow [lexical].
|
|
///
|
|
/// At most one will be created per run of LexicalDestroyFolding.
|
|
///
|
|
/// returns false if the move_value [lexical] instruction was added already
|
|
/// true if the move_value [lexical] instruction was added just now
|
|
bool createMove();
|
|
|
|
/// Combine the matched instruction sequence
|
|
///
|
|
/// apply %fn(%copy)
|
|
/// end_borrow %lifetime
|
|
/// destroy_value %instance
|
|
///
|
|
/// into
|
|
///
|
|
/// end_borrow %lifetime
|
|
/// apply %fn(%move)
|
|
///
|
|
/// This is done in three steps:
|
|
/// (1) rewrite the apply (and delete the copy_value that is fed to it if
|
|
/// possible)
|
|
/// (2) hoist the end_borrow
|
|
/// (3) delete the destroy_value
|
|
void fold(Match, ArrayRef<int> rewritableArgumentIndices);
|
|
};
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Driver
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Perform any possible folding.
|
|
///
|
|
/// Returns whether any change was made.
|
|
MoveValueInst *run(Context &context) {
|
|
Candidates candidates;
|
|
|
|
// Do a cheap search for scope ending instructions that could potentially be
|
|
// candidates for folding.
|
|
if (!FindCandidates(context).run(candidates))
|
|
return nullptr;
|
|
|
|
// At least one full match was found and more expensive checks on the matches
|
|
// are in order.
|
|
|
|
BorroweeUsage borroweeUsage;
|
|
if (!findBorroweeUsage(context, borroweeUsage))
|
|
return nullptr;
|
|
IntroducerUsage introducerUsage;
|
|
if (!findIntroducerUsage(context, introducerUsage))
|
|
return nullptr;
|
|
|
|
// Now, filter the candidates using those values.
|
|
if (!FilterCandidates(context, introducerUsage, borroweeUsage)
|
|
.run(candidates))
|
|
return nullptr;
|
|
|
|
// Finally, check that %borrowee has no uses within %lifetime's
|
|
// borrow scope.
|
|
if (borroweeHasUsesWithinBorrowScope(context, borroweeUsage))
|
|
return nullptr;
|
|
|
|
// It is safe to rewrite the viable candidates. Do so.
|
|
return Rewriter(context, candidates).run();
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Rewriting
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
MoveValueInst *Rewriter::run() {
|
|
bool foldedAny = false;
|
|
(void)foldedAny;
|
|
auto size = candidates.vector.size();
|
|
for (unsigned index = 0; index < size; ++index) {
|
|
auto candidate = candidates.vector[index];
|
|
createMove();
|
|
if (!candidate.viable) {
|
|
// Nonviable candidates still end with the pattern
|
|
//
|
|
// end_borrow %lifetime
|
|
// ...
|
|
// destroy_value %borrowee
|
|
//
|
|
// Now that the new move_value [lexical] dominates all candidates, the
|
|
// every candidate's destroy_value %borrowee is dominated by it, so every
|
|
// one is dominated by another consuming use which is illegal. Rewrite
|
|
// each such destroy_value to be a destroy_value of the move.
|
|
candidate.match.dvi->setOperand(mvi);
|
|
continue;
|
|
}
|
|
|
|
fold(candidate.match, candidate.argumentIndices);
|
|
#ifndef NDEBUG
|
|
foldedAny = true;
|
|
#endif
|
|
}
|
|
assert(foldedAny && "rewriting without anything to rewrite!?");
|
|
return mvi;
|
|
}
|
|
|
|
bool Rewriter::createMove() {
|
|
// We only will create a single MoveValueInst.
|
|
if (mvi)
|
|
return false;
|
|
|
|
auto introducerBuilder = SILBuilderWithScope(context.introducer);
|
|
mvi = introducerBuilder.createMoveValue(
|
|
RegularLocation::getAutoGeneratedLocation(context.introducer->getLoc()),
|
|
context.borrowee, IsLexical);
|
|
context.introducer->setOperand(mvi);
|
|
return true;
|
|
}
|
|
|
|
void Rewriter::fold(Match candidate, ArrayRef<int> rewritableArgumentIndices) {
|
|
// First, rewrite the apply in terms of the move_value.
|
|
unsigned argumentNumber = 0;
|
|
for (auto index : rewritableArgumentIndices) {
|
|
auto argument = candidate.ai->getArgument(index);
|
|
auto *cvi = cast<CopyValueInst>(argument);
|
|
if (argumentNumber == 0) {
|
|
candidate.ai->setArgument(index, mvi);
|
|
if (!context.deleter.deleteIfDead(cvi)) {
|
|
// We can't delete the copy_value because it has other users. Instead,
|
|
// add a compensating destroy just before the apply.
|
|
auto applyBuilder = SILBuilderWithScope(candidate.ai);
|
|
applyBuilder.createDestroyValue(
|
|
RegularLocation::getAutoGeneratedLocation(candidate.ai->getLoc()),
|
|
cvi);
|
|
}
|
|
} else {
|
|
cvi->setOperand(mvi);
|
|
}
|
|
++argumentNumber;
|
|
}
|
|
|
|
// At this point, we have something along the lines of
|
|
//
|
|
// %move = move_value [lexical] %borrowee
|
|
// %lifetime = begin_borrow [lexical] %move
|
|
// ...
|
|
// apply %fn(%move)
|
|
// end_borrow %lifetime
|
|
//
|
|
// This isn't valid, though, because the apply consumes the %move but
|
|
// the borrow scope guarantees it until the subsequent end_borrow.
|
|
//
|
|
// Fix this by hoisting the end_borrow above the apply.
|
|
auto applyBuilder = SILBuilderWithScope(candidate.ai);
|
|
applyBuilder.createEndBorrow(
|
|
RegularLocation::getAutoGeneratedLocation(candidate.ai->getLoc()),
|
|
context.introducer);
|
|
context.deleter.forceDelete(candidate.ebi);
|
|
|
|
// We have introduced a consuming use of %borrowee--the move_value-- and we
|
|
// just rewrote the apply to consume it. Delete the old destroy_value.
|
|
context.deleter.forceDelete(candidate.dvi);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Lookups
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
bool FindCandidates::run(Candidates &candidates) {
|
|
llvm::SmallVector<SILInstruction *, 16> scopeEndingInsts;
|
|
context.borrowedValue.getLocalScopeEndingInstructions(scopeEndingInsts);
|
|
|
|
bool foundAnyFull = false;
|
|
|
|
for (auto *instruction : scopeEndingInsts) {
|
|
if (auto match = definesMatchingInstructionSequence(instruction)) {
|
|
assert(match->ebi->getOperand() == context.introducer);
|
|
assert(match->dvi->getOperand() == context.borrowee);
|
|
candidates.vector.push_back({*match, false, {}});
|
|
|
|
foundAnyFull = foundAnyFull || match->isFullMatch();
|
|
} else {
|
|
// The instruction doesn't define even a partial match. Either the scope
|
|
// ending instruction isn't an end_borrow or the subsequent instruction
|
|
// isn't a destroy_value. We can't fold any applies.
|
|
return false;
|
|
}
|
|
}
|
|
return foundAnyFull;
|
|
}
|
|
|
|
bool findIntroducerUsage(Context const &context, IntroducerUsage &usage) {
|
|
SmallVector<Operand *, 16> useVector;
|
|
if (!findExtendedUsesOfSimpleBorrowedValue(context.borrowedValue,
|
|
&useVector)) {
|
|
// If the value produced by begin_borrow escapes, don't shrink the borrow
|
|
// scope over the apply.
|
|
return false;
|
|
}
|
|
for (auto *use : useVector) {
|
|
usage.uses.insert(use);
|
|
usage.users.insert(use->getUser());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FilterCandidates::run(Candidates &candidates) {
|
|
bool anyViable = false;
|
|
|
|
// We have some end_borrows that might be candidates for folding.
|
|
for (unsigned index = 0, count = candidates.vector.size(); index < count;
|
|
++index) {
|
|
auto &candidate = candidates.vector[index];
|
|
SmallVector<int, 2> rewritableArgumentIndices;
|
|
auto viability = isViableMatch(candidate.match, candidate.argumentIndices);
|
|
switch (viability) {
|
|
case MatchViability::Viable:
|
|
candidate.viable = true;
|
|
anyViable = true;
|
|
break;
|
|
case MatchViability::Nonviable:
|
|
break;
|
|
case MatchViability::Illegal:
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return anyViable;
|
|
}
|
|
|
|
bool findBorroweeUsage(Context const &context, BorroweeUsage &usage) {
|
|
auto recordUse = [&](Operand *use) {
|
|
// Ignore uses that aren't dominated by the introducer. PrunedLiveness
|
|
// relies on us doing this check.
|
|
if (!context.dominanceTree.dominates(context.introducer, use->getUser()))
|
|
return;
|
|
usage.uses.push_back(use);
|
|
usage.users.insert(use->getUser());
|
|
};
|
|
for (auto *use : context.borrowee->getUses()) {
|
|
auto *user = use->getUser();
|
|
if (user == context.introducer)
|
|
continue;
|
|
switch (use->getOperandOwnership()) {
|
|
case OperandOwnership::PointerEscape:
|
|
return false;
|
|
case OperandOwnership::Borrow:
|
|
if (!BorrowingOperand(use).visitScopeEndingUses([&](Operand *end) {
|
|
if (end->getOperandOwnership() == OperandOwnership::Reborrow) {
|
|
return false;
|
|
}
|
|
if (PhiOperand(end)) {
|
|
assert(end->getOperandOwnership() ==
|
|
OperandOwnership::ForwardingConsume);
|
|
return false;
|
|
}
|
|
recordUse(end);
|
|
return true;
|
|
})) {
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
recordUse(use);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool borroweeHasUsesWithinBorrowScope(Context const &context,
|
|
BorroweeUsage const &usage) {
|
|
MultiDefPrunedLiveness liveness(context.function);
|
|
context.borrowedValue.computeTransitiveLiveness(liveness);
|
|
DeadEndBlocks deadEndBlocks(context.function);
|
|
return !liveness.areUsesOutsideBoundary(usage.uses, &deadEndBlocks);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Predicates
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
std::optional<Match>
|
|
FindCandidates::definesMatchingInstructionSequence(SILInstruction *inst) const {
|
|
// Look specifically for
|
|
//
|
|
// apply
|
|
// end_borrow // inst
|
|
// ...
|
|
// destroy_value %borrowee
|
|
auto *ebi = dyn_cast<EndBorrowInst>(inst);
|
|
if (!ebi)
|
|
return std::nullopt;
|
|
auto *dvi = findNextBorroweeDestroy(ebi->getNextInstruction());
|
|
if (!dvi)
|
|
return std::nullopt;
|
|
auto *ai = dyn_cast_or_null<ApplyInst>(ebi->getPreviousInstruction());
|
|
if (!ai)
|
|
return {{nullptr, ebi, dvi}};
|
|
|
|
return {{ai, ebi, dvi}};
|
|
}
|
|
|
|
DestroyValueInst *
|
|
FindCandidates::findNextBorroweeDestroy(SILInstruction *from) const {
|
|
for (auto *inst = from; inst; inst = inst->getNextInstruction()) {
|
|
if (!OSSACanonicalizeOwned::ignoredByDestroyHoisting(inst->getKind())) {
|
|
// This is not an instruction that OSSACanonicalizeOwned would not
|
|
// hoist a destroy above. In other words, OSSACanonicalizeOwned would
|
|
// have hoisted
|
|
// destroy_value %borrowee
|
|
// over this instruction if it could have. Stop looking.
|
|
return nullptr;
|
|
}
|
|
if (auto *dvi = dyn_cast<DestroyValueInst>(inst)) {
|
|
if (dvi->getOperand() == context.borrowee) {
|
|
return dvi;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
MatchViability FilterCandidates::isViableMatch(
|
|
Match &candidate, SmallVectorImpl<int> &rewritableArgumentIndices) const {
|
|
for (SILInstruction *inst = candidate.ebi; inst != candidate.dvi;
|
|
inst = inst->getNextInstruction()) {
|
|
if (borroweeUsage.users.contains(inst)) {
|
|
// In the sequence of instructions
|
|
// end_borrow %lifetime
|
|
// ...
|
|
// destroy_value %borrowee
|
|
// we have found a user of %borrowee. That existing means not
|
|
// only that this candidate is not viable but that NONE of the candidates
|
|
// are viable and we need to bail out completely.
|
|
return MatchViability::Illegal;
|
|
}
|
|
}
|
|
|
|
// Now that we've checked that this partial match isn't illegal, go ahead and
|
|
// discard it.
|
|
if (!candidate.isFullMatch())
|
|
return MatchViability::Nonviable;
|
|
|
|
// If the apply isn't a user of the extended simple value, then this
|
|
// transformation can't be done: the callee can't end the lexical lifetime
|
|
// of a value it doesn't see. Presumably, this apply is a deinit barrier.
|
|
if (!introducerUsage.users.contains(candidate.ai))
|
|
return MatchViability::Nonviable;
|
|
|
|
// We aren't able to rewrite every simple extended use of %lifetime
|
|
// in the apply.
|
|
if (!rewritableArgumentIndicesForApply(candidate.ai,
|
|
rewritableArgumentIndices))
|
|
return MatchViability::Nonviable;
|
|
|
|
return MatchViability::Viable;
|
|
}
|
|
|
|
bool FilterCandidates::isSimpleExtendedIntroducerDef(SILValue value) const {
|
|
while (true) {
|
|
auto *instruction = value.getDefiningInstruction();
|
|
if (!instruction)
|
|
return false;
|
|
if (instruction == context.introducer)
|
|
return true;
|
|
if (auto *cvi = dyn_cast<CopyValueInst>(instruction)) {
|
|
value = cvi->getOperand();
|
|
continue;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FilterCandidates::rewritableArgumentIndicesForApply(
|
|
ApplySite apply, SmallVectorImpl<int> &indices) const {
|
|
for (auto &operand : apply->getAllOperands()) {
|
|
if (introducerUsage.uses.contains(&operand)) {
|
|
if (apply.isArgumentOperand(operand)) {
|
|
auto convention = apply.getArgumentConvention(operand);
|
|
if (isSimpleExtendedIntroducerDef(operand.get()) &&
|
|
convention.isOwnedConventionInCaller()) {
|
|
indices.push_back(apply.getCalleeArgIndex(operand));
|
|
} else {
|
|
// This argument is a use of %lifetime but not an owned use that we
|
|
// can rewrite.
|
|
return false;
|
|
}
|
|
} else {
|
|
// If %lifetime is used in some non-argument position, e.g. as the
|
|
// callee, we can't fold.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace LexicalDestroyFolding
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Entry Point
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// The entry point.
|
|
MoveValueInst *
|
|
swift::foldDestroysOfCopiedLexicalBorrow(BeginBorrowInst *bbi,
|
|
DominanceInfo &dominanceTree,
|
|
InstructionDeleter &deleter) {
|
|
if (!bbi->isLexical())
|
|
return nullptr;
|
|
if (bbi->getOperand()->getOwnershipKind() != OwnershipKind::Owned)
|
|
return nullptr;
|
|
if (!dominanceTree.isReachableFromEntry(bbi->getParentBlock()))
|
|
return nullptr;
|
|
|
|
auto context = LexicalDestroyFolding::Context(bbi, dominanceTree, deleter);
|
|
return LexicalDestroyFolding::run(context);
|
|
}
|
|
|
|
namespace swift::test {
|
|
// Arguments:
|
|
// - the lexical borrow to fold
|
|
// Dumps:
|
|
// - the function
|
|
static FunctionTest LexicalDestroyFoldingTest(
|
|
"lexical_destroy_folding", [](auto &function, auto &arguments, auto &test) {
|
|
auto *dominanceAnalysis = test.template getAnalysis<DominanceAnalysis>();
|
|
DominanceInfo *domTree = dominanceAnalysis->get(&function);
|
|
auto value = arguments.takeValue();
|
|
auto *bbi = cast<BeginBorrowInst>(value);
|
|
InstructionDeleter deleter;
|
|
foldDestroysOfCopiedLexicalBorrow(bbi, *domTree, deleter);
|
|
function.print(llvm::outs());
|
|
});
|
|
} // end namespace swift::test
|