mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Specifically if we have a begin_access [read] or a begin_access [modify] that is never actually written to. In that case if we can prove that the load [copy] is completely within the exclusive access region, we will load_borrow. Example: ``` %0 = begin_access [read] ... %1 = load [copy] %0 ... destroy_value %1 end_access %0 ``` => ``` %0 = begin_access [read] ... %1 = load_borrow %0 ... end_borrow %1 end_access %0 ``` rdar://60064692
1382 lines
51 KiB
C++
1382 lines
51 KiB
C++
//===--- SemanticARCOpts.cpp ----------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#define DEBUG_TYPE "sil-semantic-arc-opts"
|
|
#include "swift/Basic/BlotSetVector.h"
|
|
#include "swift/Basic/STLExtras.h"
|
|
#include "swift/SIL/BasicBlockUtils.h"
|
|
#include "swift/SIL/DebugUtils.h"
|
|
#include "swift/SIL/LinearLifetimeChecker.h"
|
|
#include "swift/SIL/MemAccessUtils.h"
|
|
#include "swift/SIL/OwnershipUtils.h"
|
|
#include "swift/SIL/Projection.h"
|
|
#include "swift/SIL/SILArgument.h"
|
|
#include "swift/SIL/SILBuilder.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/SILVisitor.h"
|
|
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
|
|
#include "swift/SILOptimizer/PassManager/Passes.h"
|
|
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
|
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
|
|
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
|
|
#include "llvm/ADT/SmallPtrSet.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/Statistic.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
|
|
using namespace swift;
|
|
|
|
STATISTIC(NumEliminatedInsts, "number of removed instructions");
|
|
STATISTIC(NumLoadCopyConvertedToLoadBorrow,
|
|
"number of load_copy converted to load_borrow");
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Live Range Modeling
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
class LiveRange {
|
|
/// A list of destroy_values of the live range.
|
|
SmallVector<Operand *, 2> destroyingUses;
|
|
|
|
/// A list of forwarding instructions that forward our destroys ownership, but
|
|
/// that are also able to forward guaranteed ownership.
|
|
SmallVector<Operand *, 2> generalForwardingUses;
|
|
|
|
/// Consuming users 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.
|
|
SmallVector<Operand *, 2> unknownConsumingUses;
|
|
|
|
public:
|
|
LiveRange(SILValue value);
|
|
LiveRange(const LiveRange &) = delete;
|
|
LiveRange &operator=(const LiveRange &) = delete;
|
|
|
|
/// 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.
|
|
bool hasConsumingUse() const { return unknownConsumingUses.size(); }
|
|
|
|
ArrayRef<Operand *> getDestroyingUses() const { return destroyingUses; }
|
|
|
|
private:
|
|
struct OperandToUser;
|
|
|
|
public:
|
|
using DestroyingInstsRange =
|
|
TransformRange<ArrayRef<Operand *>, OperandToUser>;
|
|
DestroyingInstsRange getDestroyingInsts() const;
|
|
|
|
ArrayRef<Operand *> getNonConsumingForwardingUses() const {
|
|
return generalForwardingUses;
|
|
}
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
struct LiveRange::OperandToUser {
|
|
OperandToUser() {}
|
|
|
|
SILInstruction *operator()(const Operand *use) const {
|
|
auto *nonConstUse = const_cast<Operand *>(use);
|
|
return nonConstUse->getUser();
|
|
}
|
|
};
|
|
|
|
LiveRange::DestroyingInstsRange LiveRange::getDestroyingInsts() const {
|
|
return DestroyingInstsRange(getDestroyingUses(), OperandToUser());
|
|
}
|
|
|
|
LiveRange::LiveRange(SILValue value)
|
|
: destroyingUses(), generalForwardingUses(), unknownConsumingUses() {
|
|
assert(value.getOwnershipKind() == ValueOwnershipKind::Owned);
|
|
|
|
// We know that our silvalue produces an @owned value. Look through all of our
|
|
// uses and classify them as either consuming or not.
|
|
SmallVector<Operand *, 32> worklist(value->getUses());
|
|
while (!worklist.empty()) {
|
|
auto *op = worklist.pop_back_val();
|
|
|
|
// Skip type dependent operands.
|
|
if (op->isTypeDependent())
|
|
continue;
|
|
|
|
// Do a quick check that we did not add ValueOwnershipKind that are not
|
|
// owned to the worklist.
|
|
assert(op->get().getOwnershipKind() == ValueOwnershipKind::Owned &&
|
|
"Added non-owned value to worklist?!");
|
|
|
|
auto *user = op->getUser();
|
|
|
|
// Ok, this constraint can take something owned as live. Assert that it
|
|
// can also accept something that is guaranteed. Any non-consuming use of
|
|
// an owned value should be able to take a guaranteed parameter as well
|
|
// (modulo bugs). We assert to catch these.
|
|
if (!op->isConsumingUse()) {
|
|
continue;
|
|
}
|
|
|
|
// Ok, we know now that we have a consuming use. See if we have a destroy
|
|
// value, quickly up front. If we do have one, stash it and continue.
|
|
if (isa<DestroyValueInst>(user)) {
|
|
destroyingUses.push_back(op);
|
|
continue;
|
|
}
|
|
|
|
// Otherwise, see if we have a forwarding value that has a single
|
|
// non-trivial operand that can accept a guaranteed value. If not, we can
|
|
// not recursively process it, so be conservative and assume that we /may
|
|
// consume/ the value, so the live range must not be eliminated.
|
|
//
|
|
// DISCUSSION: For now we do not support forwarding instructions with
|
|
// multiple non-trivial arguments since we would need to optimize all of
|
|
// the non-trivial arguments at the same time.
|
|
//
|
|
// NOTE: Today we do not support TermInsts for simplicity... we /could/
|
|
// support it though if we need to.
|
|
auto *ti = dyn_cast<TermInst>(user);
|
|
if ((ti && !ti->isTransformationTerminator()) ||
|
|
!isGuaranteedForwardingInst(user) ||
|
|
1 != count_if(user->getOperandValues(
|
|
true /*ignore type dependent operands*/),
|
|
[&](SILValue v) {
|
|
return v.getOwnershipKind() ==
|
|
ValueOwnershipKind::Owned;
|
|
})) {
|
|
unknownConsumingUses.push_back(op);
|
|
continue;
|
|
}
|
|
|
|
// Ok, this is a forwarding instruction whose ownership we can flip from
|
|
// owned -> guaranteed.
|
|
generalForwardingUses.push_back(op);
|
|
|
|
// If we have a non-terminator, just visit its users recursively to see if
|
|
// the the users force the live range to be alive.
|
|
if (!ti) {
|
|
for (SILValue v : user->getResults()) {
|
|
if (v.getOwnershipKind() != ValueOwnershipKind::Owned)
|
|
continue;
|
|
llvm::copy(v->getUses(), std::back_inserter(worklist));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Otherwise, we know that we have no only a terminator, but a
|
|
// transformation terminator, so we should add the users of its results to
|
|
// the worklist.
|
|
for (auto &succ : ti->getSuccessors()) {
|
|
auto *succBlock = succ.getBB();
|
|
|
|
// If we do not have any arguments, then continue.
|
|
if (succBlock->args_empty())
|
|
continue;
|
|
|
|
for (auto *succArg : succBlock->getSILPhiArguments()) {
|
|
// If we have an any value, just continue.
|
|
if (succArg->getOwnershipKind() == ValueOwnershipKind::None)
|
|
continue;
|
|
|
|
// Otherwise add all users of this BBArg to the worklist to visit
|
|
// recursively.
|
|
llvm::copy(succArg->getUses(), std::back_inserter(worklist));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Address Written To Analysis
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
/// A simple analysis that checks if a specific def (in our case an inout
|
|
/// argument) is ever written to. This is conservative, local and processes
|
|
/// recursively downwards from def->use.
|
|
struct IsAddressWrittenToDefUseAnalysis {
|
|
llvm::SmallDenseMap<SILValue, bool, 8> isWrittenToCache;
|
|
|
|
bool operator()(SILValue value) {
|
|
auto iter = isWrittenToCache.try_emplace(value, true);
|
|
|
|
// If we are already in the map, just return that.
|
|
if (!iter.second)
|
|
return iter.first->second;
|
|
|
|
// Otherwise, compute our value, cache it and return.
|
|
bool result = isWrittenToHelper(value);
|
|
iter.first->second = result;
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
bool isWrittenToHelper(SILValue value);
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
bool IsAddressWrittenToDefUseAnalysis::isWrittenToHelper(SILValue initialValue) {
|
|
SmallVector<Operand *, 8> worklist(initialValue->getUses());
|
|
while (!worklist.empty()) {
|
|
auto *op = worklist.pop_back_val();
|
|
SILInstruction *user = op->getUser();
|
|
|
|
if (Projection::isAddressProjection(user) ||
|
|
isa<ProjectBlockStorageInst>(user)) {
|
|
for (SILValue r : user->getResults()) {
|
|
llvm::copy(r->getUses(), std::back_inserter(worklist));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (auto *oeai = dyn_cast<OpenExistentialAddrInst>(user)) {
|
|
// Mutable access!
|
|
if (oeai->getAccessKind() != OpenedExistentialAccess::Immutable) {
|
|
return true;
|
|
}
|
|
|
|
// Otherwise, look through it and continue.
|
|
llvm::copy(oeai->getUses(), std::back_inserter(worklist));
|
|
continue;
|
|
}
|
|
|
|
// load_borrow and incidental uses are fine as well.
|
|
if (isa<LoadBorrowInst>(user) || isIncidentalUse(user)) {
|
|
continue;
|
|
}
|
|
|
|
// Look through immutable begin_access.
|
|
if (auto *bai = dyn_cast<BeginAccessInst>(user)) {
|
|
// If we do not have a read, return true.
|
|
if (bai->getAccessKind() != SILAccessKind::Read) {
|
|
return true;
|
|
}
|
|
|
|
// Otherwise, add the users to the worklist and continue.
|
|
llvm::copy(bai->getUses(), std::back_inserter(worklist));
|
|
continue;
|
|
}
|
|
|
|
// As long as we do not have a load [take], we are fine.
|
|
if (auto *li = dyn_cast<LoadInst>(user)) {
|
|
if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Take) {
|
|
return true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// If we have a FullApplySite, see if we use the value as an
|
|
// indirect_guaranteed parameter. If we use it as inout, we need
|
|
// interprocedural analysis that we do not perform here.
|
|
if (auto fas = FullApplySite::isa(user)) {
|
|
if (fas.getArgumentConvention(*op) ==
|
|
SILArgumentConvention::Indirect_In_Guaranteed)
|
|
continue;
|
|
|
|
// Otherwise, be conservative and return true.
|
|
return true;
|
|
}
|
|
|
|
// Copy addr that read are just loads.
|
|
if (auto *cai = dyn_cast<CopyAddrInst>(user)) {
|
|
// If our value is the destination, this is a write.
|
|
if (cai->getDest() == op->get()) {
|
|
return true;
|
|
}
|
|
|
|
// Ok, so we are Src by process of elimination. Make sure we are not being
|
|
// taken.
|
|
if (cai->isTakeOfSrc()) {
|
|
return true;
|
|
}
|
|
|
|
// Otherwise, we are safe and can continue.
|
|
continue;
|
|
}
|
|
|
|
// If we did not recognize the user, just return conservatively that it was
|
|
// written to.
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "Function: " << user->getFunction()->getName() << "\n");
|
|
LLVM_DEBUG(llvm::dbgs() << "Value: " << op->get());
|
|
LLVM_DEBUG(llvm::dbgs() << "Unknown instruction!: " << *user);
|
|
return true;
|
|
}
|
|
|
|
// Ok, we finished our worklist and this address is not being written to.
|
|
return false;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Convert Forwarding Insts from Owned -> Guaranteed
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static void convertForwardingUsesFromOwnedToGuaranteed(
|
|
ArrayRef<Operand *> guaranteedForwardingUses) {
|
|
// Then change all of our guaranteed forwarding insts to have guaranteed
|
|
// ownership kind instead of what ever they previously had (ignoring trivial
|
|
// results);
|
|
while (!guaranteedForwardingUses.empty()) {
|
|
auto *use = guaranteedForwardingUses.back();
|
|
guaranteedForwardingUses = guaranteedForwardingUses.drop_back();
|
|
|
|
auto *i = use->getUser();
|
|
|
|
// If this is a term inst, just convert all of its incoming values that are
|
|
// owned to be guaranteed.
|
|
if (auto *ti = dyn_cast<TermInst>(i)) {
|
|
for (auto &succ : ti->getSuccessors()) {
|
|
auto *succBlock = succ.getBB();
|
|
|
|
// If we do not have any arguments, then continue.
|
|
if (succBlock->args_empty())
|
|
continue;
|
|
|
|
for (auto *succArg : succBlock->getSILPhiArguments()) {
|
|
// If we have an any value, just continue.
|
|
if (succArg->getOwnershipKind() == ValueOwnershipKind::Owned) {
|
|
succArg->setOwnershipKind(ValueOwnershipKind::Guaranteed);
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
assert(i->hasResults());
|
|
for (SILValue result : i->getResults()) {
|
|
if (auto *svi = dyn_cast<OwnershipForwardingSingleValueInst>(result)) {
|
|
if (svi->getOwnershipKind() == ValueOwnershipKind::Owned) {
|
|
svi->setOwnershipKind(ValueOwnershipKind::Guaranteed);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (auto *ofci = dyn_cast<OwnershipForwardingConversionInst>(result)) {
|
|
if (ofci->getOwnershipKind() == ValueOwnershipKind::Owned) {
|
|
ofci->setOwnershipKind(ValueOwnershipKind::Guaranteed);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (auto *sei = dyn_cast<OwnershipForwardingSelectEnumInstBase>(result)) {
|
|
if (sei->getOwnershipKind() == ValueOwnershipKind::Owned) {
|
|
sei->setOwnershipKind(ValueOwnershipKind::Guaranteed);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (auto *mvir = dyn_cast<MultipleValueInstructionResult>(result)) {
|
|
if (mvir->getOwnershipKind() == ValueOwnershipKind::Owned) {
|
|
mvir->setOwnershipKind(ValueOwnershipKind::Guaranteed);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
llvm_unreachable("unhandled forwarding instruction?!");
|
|
}
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Implementation
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
/// A visitor that optimizes ownership instructions and eliminates any trivially
|
|
/// dead code that results after optimization. It uses an internal worklist that
|
|
/// is initialized on construction with targets to avoid iterator invalidation
|
|
/// issues. Rather than revisit the entire CFG like SILCombine and other
|
|
/// visitors do, we maintain a visitedSinceLastMutation list to ensure that we
|
|
/// revisit all interesting instructions in between mutations.
|
|
struct SemanticARCOptVisitor
|
|
: SILInstructionVisitor<SemanticARCOptVisitor, bool> {
|
|
/// Our main worklist. We use this after an initial run through.
|
|
SmallBlotSetVector<SILValue, 32> worklist;
|
|
|
|
/// A set of values that we have visited since the last mutation. We use this
|
|
/// to ensure that we do not visit values twice without mutating.
|
|
///
|
|
/// This is specifically to ensure that we do not go into an infinite loop
|
|
/// when visiting phi nodes.
|
|
SmallBlotSetVector<SILValue, 16> visitedSinceLastMutation;
|
|
|
|
SILFunction &F;
|
|
Optional<DeadEndBlocks> TheDeadEndBlocks;
|
|
ValueLifetimeAnalysis::Frontier lifetimeFrontier;
|
|
IsAddressWrittenToDefUseAnalysis isAddressWrittenToDefUseAnalysis;
|
|
|
|
explicit SemanticARCOptVisitor(SILFunction &F) : F(F) {}
|
|
|
|
DeadEndBlocks &getDeadEndBlocks() {
|
|
if (!TheDeadEndBlocks)
|
|
TheDeadEndBlocks.emplace(&F);
|
|
return *TheDeadEndBlocks;
|
|
}
|
|
|
|
/// Given a single value instruction, RAUW it with newValue, add newValue to
|
|
/// the worklist, and then call eraseInstruction on i.
|
|
void eraseAndRAUWSingleValueInstruction(SingleValueInstruction *i, SILValue newValue) {
|
|
worklist.insert(newValue);
|
|
for (auto *use : i->getUses()) {
|
|
for (SILValue result : use->getUser()->getResults()) {
|
|
worklist.insert(result);
|
|
}
|
|
}
|
|
i->replaceAllUsesWith(newValue);
|
|
eraseInstructionAndAddOperandsToWorklist(i);
|
|
}
|
|
|
|
/// Add all operands of i to the worklist and then call eraseInstruction on
|
|
/// i. Assumes that the instruction doesnt have users.
|
|
void eraseInstructionAndAddOperandsToWorklist(SILInstruction *i) {
|
|
// Then copy all operands into the worklist for future processing.
|
|
for (SILValue v : i->getOperandValues()) {
|
|
worklist.insert(v);
|
|
}
|
|
eraseInstruction(i);
|
|
}
|
|
|
|
/// Remove all results of the given instruction from the worklist and then
|
|
/// erase the instruction. Assumes that the instruction does not have any
|
|
/// users left.
|
|
void eraseInstruction(SILInstruction *i) {
|
|
// Remove all SILValues of the instruction from the worklist and then erase
|
|
// the instruction.
|
|
for (SILValue result : i->getResults()) {
|
|
worklist.erase(result);
|
|
visitedSinceLastMutation.erase(result);
|
|
}
|
|
i->eraseFromParent();
|
|
|
|
// Add everything else from visitedSinceLastMutation to the worklist.
|
|
for (auto opt : visitedSinceLastMutation) {
|
|
if (!opt.hasValue()) {
|
|
continue;
|
|
}
|
|
worklist.insert(*opt);
|
|
}
|
|
visitedSinceLastMutation.clear();
|
|
}
|
|
|
|
/// The default visitor.
|
|
bool visitSILInstruction(SILInstruction *i) {
|
|
assert(!isGuaranteedForwardingInst(i) &&
|
|
"Should have forwarding visitor for all ownership forwarding "
|
|
"instructions");
|
|
return false;
|
|
}
|
|
|
|
bool visitCopyValueInst(CopyValueInst *cvi);
|
|
bool visitBeginBorrowInst(BeginBorrowInst *bbi);
|
|
bool visitLoadInst(LoadInst *li);
|
|
static bool shouldVisitInst(SILInstruction *i) {
|
|
switch (i->getKind()) {
|
|
default:
|
|
return false;
|
|
case SILInstructionKind::CopyValueInst:
|
|
case SILInstructionKind::BeginBorrowInst:
|
|
case SILInstructionKind::LoadInst:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#define FORWARDING_INST(NAME) \
|
|
bool visit##NAME##Inst(NAME##Inst *cls) { \
|
|
for (SILValue v : cls->getResults()) { \
|
|
worklist.insert(v); \
|
|
} \
|
|
return false; \
|
|
}
|
|
FORWARDING_INST(Tuple)
|
|
FORWARDING_INST(Struct)
|
|
FORWARDING_INST(Enum)
|
|
FORWARDING_INST(OpenExistentialRef)
|
|
FORWARDING_INST(Upcast)
|
|
FORWARDING_INST(UncheckedRefCast)
|
|
FORWARDING_INST(ConvertFunction)
|
|
FORWARDING_INST(RefToBridgeObject)
|
|
FORWARDING_INST(BridgeObjectToRef)
|
|
FORWARDING_INST(UnconditionalCheckedCast)
|
|
FORWARDING_INST(UncheckedEnumData)
|
|
FORWARDING_INST(MarkUninitialized)
|
|
FORWARDING_INST(SelectEnum)
|
|
FORWARDING_INST(DestructureStruct)
|
|
FORWARDING_INST(DestructureTuple)
|
|
FORWARDING_INST(TupleExtract)
|
|
FORWARDING_INST(StructExtract)
|
|
FORWARDING_INST(OpenExistentialValue)
|
|
FORWARDING_INST(OpenExistentialBoxValue)
|
|
FORWARDING_INST(MarkDependence)
|
|
#undef FORWARDING_INST
|
|
|
|
#define FORWARDING_TERM(NAME) \
|
|
bool visit##NAME##Inst(NAME##Inst *cls) { \
|
|
for (auto succValues : cls->getSuccessorBlockArgumentLists()) { \
|
|
for (SILValue v : succValues) { \
|
|
worklist.insert(v); \
|
|
} \
|
|
} \
|
|
return false; \
|
|
}
|
|
|
|
FORWARDING_TERM(SwitchEnum)
|
|
FORWARDING_TERM(CheckedCastBranch)
|
|
FORWARDING_TERM(Branch)
|
|
FORWARDING_TERM(CondBranch)
|
|
#undef FORWARDING_TERM
|
|
|
|
bool isWrittenTo(LoadInst *li, const LiveRange &lr);
|
|
|
|
bool processWorklist();
|
|
|
|
bool performGuaranteedCopyValueOptimization(CopyValueInst *cvi);
|
|
bool eliminateDeadLiveRangeCopyValue(CopyValueInst *cvi);
|
|
bool tryJoiningCopyValueLiveRangeWithOperand(CopyValueInst *cvi);
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
static llvm::cl::opt<bool>
|
|
VerifyAfterTransform("sil-semantic-arc-opts-verify-after-transform",
|
|
llvm::cl::init(false), llvm::cl::Hidden);
|
|
|
|
bool SemanticARCOptVisitor::processWorklist() {
|
|
// NOTE: The madeChange here is not strictly necessary since we only have
|
|
// items added to the worklist today if we have already made /some/ sort of
|
|
// change. That being said, I think there is a low cost to including this here
|
|
// and makes the algorithm more correct, visually and in the face of potential
|
|
// refactoring.
|
|
bool madeChange = false;
|
|
|
|
while (!worklist.empty()) {
|
|
// Pop the last element off the list. If we were returned None, we blotted
|
|
// this element, so skip it.
|
|
SILValue next = worklist.pop_back_val().getValueOr(SILValue());
|
|
if (!next)
|
|
continue;
|
|
|
|
// First check if this is a value that we have visited since the last time
|
|
// we erased an instruction. If we have visited it, skip it. Every time we
|
|
// modify something, we should be deleting an instruction, so we have not
|
|
// found any further information.
|
|
if (!visitedSinceLastMutation.insert(next).second) {
|
|
continue;
|
|
}
|
|
|
|
// First check if this is an instruction that is trivially dead. This can
|
|
// occur if we eliminate rr traffic resulting in dead projections and the
|
|
// like.
|
|
//
|
|
// If we delete, we first add all of our deleted instructions operands to
|
|
// the worklist and then remove all results (since we are going to delete
|
|
// the instruction).
|
|
if (auto *defInst = next->getDefiningInstruction()) {
|
|
if (isInstructionTriviallyDead(defInst)) {
|
|
deleteAllDebugUses(defInst);
|
|
eraseInstruction(defInst);
|
|
madeChange = true;
|
|
if (VerifyAfterTransform) {
|
|
F.verify();
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Otherwise, if we have a single value instruction (to be expanded later
|
|
// perhaps), try to visit that value recursively.
|
|
if (auto *svi = dyn_cast<SingleValueInstruction>(next)) {
|
|
bool madeSingleChange = visit(svi);
|
|
madeChange |= madeSingleChange;
|
|
if (VerifyAfterTransform && madeSingleChange) {
|
|
F.verify();
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return madeChange;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Redundant Borrow Scope Elimination
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
bool SemanticARCOptVisitor::visitBeginBorrowInst(BeginBorrowInst *bbi) {
|
|
auto kind = bbi->getOperand().getOwnershipKind();
|
|
SmallVector<EndBorrowInst *, 16> endBorrows;
|
|
for (auto *op : bbi->getUses()) {
|
|
if (!op->isConsumingUse()) {
|
|
// Make sure that this operand can accept our arguments kind.
|
|
auto map = op->getOwnershipKindMap();
|
|
if (map.canAcceptKind(kind))
|
|
continue;
|
|
return false;
|
|
}
|
|
|
|
// Otherwise, this borrow is being consumed. See if our consuming inst is an
|
|
// end_borrow. If it isn't, then return false, this scope is
|
|
// needed. Otherwise, add the end_borrow to our list of end borrows.
|
|
auto *ebi = dyn_cast<EndBorrowInst>(op->getUser());
|
|
if (!ebi) {
|
|
return false;
|
|
}
|
|
endBorrows.push_back(ebi);
|
|
}
|
|
|
|
// At this point, we know that the begin_borrow's operand can be
|
|
// used as an argument to all non-end borrow uses. Eliminate the
|
|
// begin borrow and end borrows.
|
|
while (!endBorrows.empty()) {
|
|
auto *ebi = endBorrows.pop_back_val();
|
|
eraseInstruction(ebi);
|
|
++NumEliminatedInsts;
|
|
}
|
|
|
|
eraseAndRAUWSingleValueInstruction(bbi, bbi->getOperand());
|
|
++NumEliminatedInsts;
|
|
return true;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// CopyValue Optimizations Elimination
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Eliminate a copy of a borrowed value, if:
|
|
//
|
|
// 1. All of the copies users do not consume the copy (and thus can accept a
|
|
// borrowed value instead).
|
|
// 2. The copies's non-destroy_value users are strictly contained within the
|
|
// scope of the borrowed value.
|
|
//
|
|
// Example:
|
|
//
|
|
// %0 = @guaranteed (argument or instruction)
|
|
// %1 = copy_value %0
|
|
// apply %f(%1) : $@convention(thin) (@guaranteed ...) ...
|
|
// other_non_consuming_use %1
|
|
// destroy_value %1
|
|
// end_borrow %0 (if an instruction)
|
|
//
|
|
// =>
|
|
//
|
|
// %0 = @guaranteed (argument or instruction)
|
|
// apply %f(%0) : $@convention(thin) (@guaranteed ...) ...
|
|
// other_non_consuming_use %0
|
|
// end_borrow %0 (if an instruction)
|
|
//
|
|
// NOTE: This means that the destroy_value technically can be after the
|
|
// end_borrow. In practice, this will not be the case but we use this to avoid
|
|
// having to reason about the ordering of the end_borrow and destroy_value.
|
|
//
|
|
// NOTE: Today we only perform this for guaranteed parameters since this enables
|
|
// us to avoid doing the linear lifetime check to make sure that all destroys
|
|
// are within the borrow scope.
|
|
//
|
|
// TODO: This needs a better name.
|
|
bool SemanticARCOptVisitor::performGuaranteedCopyValueOptimization(CopyValueInst *cvi) {
|
|
SmallVector<BorrowScopeIntroducingValue, 4> borrowScopeIntroducers;
|
|
|
|
// Find all borrow introducers for our copy operand. If we are unable to find
|
|
// all of the reproducers (due to pattern matching failure), conservatively
|
|
// return false. We can not optimize.
|
|
//
|
|
// NOTE: We can get multiple introducers if our copy_value's operand
|
|
// value runs through a phi or an aggregate forming instruction.
|
|
if (!getAllBorrowIntroducingValues(cvi->getOperand(), borrowScopeIntroducers))
|
|
return false;
|
|
|
|
// Then go over all of our uses and see if the value returned by our copy
|
|
// value forms a dead live range. If we do not have a dead live range, there
|
|
// must be some consuming use that we either do not understand is /actually/
|
|
// forwarding or a user that truly represents a necessary consume of the
|
|
// value (e.x. storing into memory).
|
|
LiveRange lr(cvi);
|
|
if (lr.hasConsumingUse())
|
|
return false;
|
|
|
|
// Next check if we do not have any destroys of our copy_value and are
|
|
// processing a local borrow scope. In such a case, due to the way we ignore
|
|
// dead end blocks, we may eliminate the copy_value, creating a use of the
|
|
// borrowed value after the end_borrow. To avoid this, in such cases we
|
|
// bail. In contrast, a non-local borrow scope does not have any end scope
|
|
// instructions, implying we can avoid this hazard and still optimize in such
|
|
// a case.
|
|
//
|
|
// DISCUSSION: Consider the following SIL:
|
|
//
|
|
// ```
|
|
// %1 = begin_borrow %0 : $KlassPair (1)
|
|
// %2 = struct_extract %1 : $KlassPair, #KlassPair.firstKlass
|
|
// %3 = copy_value %2 : $Klass
|
|
// ...
|
|
// end_borrow %1 : $LintCommand (2)
|
|
// cond_br ..., bb1, bb2
|
|
//
|
|
// ...
|
|
//
|
|
// bbN:
|
|
// // Never return type implies dead end block.
|
|
// apply %f(%3) : $@convention(thin) (@guaranteed Klass) -> Never (3)
|
|
// unreachable
|
|
// ```
|
|
//
|
|
// For simplicity, note that if bbN post-dominates %3, given that when we
|
|
// compute linear lifetime errors we ignore dead end blocks, we would not
|
|
// register that the copy_values only use is outside of the begin_borrow
|
|
// region defined by (1), (2) and thus would eliminate the copy. This would
|
|
// result in %2 being used by %f, causing the linear lifetime checker to
|
|
// error.
|
|
//
|
|
// Naively one may assume that the solution to this is to just check if %3 has
|
|
// /any/ destroy_values at all and if it doesn't have any reachable
|
|
// destroy_values, then we are in this case. But is this correct in
|
|
// general. We prove this below:
|
|
//
|
|
// The only paths along which the copy_value can not be destroyed or consumed
|
|
// is along paths to dead end blocks. Trivially, we know that such a dead end
|
|
// block, can not be reachable from the end_borrow since by their nature dead
|
|
// end blocks end in unreachables.
|
|
//
|
|
// So we know that we can only run into this bug if we have a dead end block
|
|
// reachable from the end_borrow, meaning that the bug can not occur if we
|
|
// branch before the end_borrow since in that case, the borrow scope would
|
|
// last over the dead end block's no return meaning that we will not use the
|
|
// borrowed value after its lifetime is ended by the end_borrow.
|
|
//
|
|
// With that in hand, we note again that if we have exactly one consumed,
|
|
// destroy_value /after/ the end_borrow we will not optimize here. This means
|
|
// that this bug can only occur if the copy_value is only post-dominated by
|
|
// dead end blocks that use the value in a non-consuming way.
|
|
//
|
|
// TODO: There may be some way of sinking this into the loop below.
|
|
bool haveAnyLocalScopes = llvm::any_of(
|
|
borrowScopeIntroducers, [](BorrowScopeIntroducingValue borrowScope) {
|
|
return borrowScope.isLocalScope();
|
|
});
|
|
|
|
auto destroys = lr.getDestroyingUses();
|
|
if (destroys.empty() && haveAnyLocalScopes) {
|
|
return false;
|
|
}
|
|
|
|
// If we reached this point, then we know that all of our users can accept a
|
|
// guaranteed value and our owned value is destroyed only by a set of
|
|
// destroy_values. Check if:
|
|
//
|
|
// 1. All of our destroys are joint post-dominated by our end borrow scope
|
|
// set. If they do not, then the copy_value is lifetime extending the
|
|
// guaranteed value, we can not eliminate it.
|
|
//
|
|
// 2. If all of our destroy_values are dead end. In such a case, the linear
|
|
// lifetime checker will not perform any checks since it assumes that dead
|
|
// end destroys can be ignored. Since we are going to end the program
|
|
// anyways, we want to be conservative here and optimize only if we do not
|
|
// need to insert an end_borrow since all of our borrow introducers are
|
|
// non-local scopes.
|
|
{
|
|
bool foundNonDeadEnd = false;
|
|
for (auto *d : destroys) {
|
|
foundNonDeadEnd |= !getDeadEndBlocks().isDeadEnd(d->getParentBlock());
|
|
}
|
|
if (!foundNonDeadEnd && haveAnyLocalScopes)
|
|
return false;
|
|
SmallVector<Operand *, 8> scratchSpace;
|
|
SmallPtrSet<SILBasicBlock *, 4> visitedBlocks;
|
|
if (llvm::any_of(borrowScopeIntroducers,
|
|
[&](BorrowScopeIntroducingValue borrowScope) {
|
|
return !borrowScope.areUsesWithinScope(
|
|
destroys, scratchSpace, visitedBlocks,
|
|
getDeadEndBlocks());
|
|
})) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Otherwise, we know that our copy_value/destroy_values are all completely
|
|
// within the guaranteed value scope. First delete the destroys/copies.
|
|
while (!destroys.empty()) {
|
|
auto *d = destroys.back();
|
|
destroys = destroys.drop_back();
|
|
eraseInstruction(d->getUser());
|
|
++NumEliminatedInsts;
|
|
}
|
|
|
|
eraseAndRAUWSingleValueInstruction(cvi, cvi->getOperand());
|
|
convertForwardingUsesFromOwnedToGuaranteed(
|
|
lr.getNonConsumingForwardingUses());
|
|
|
|
++NumEliminatedInsts;
|
|
return true;
|
|
}
|
|
|
|
/// If cvi only has destroy value users, then cvi is a dead live range. Lets
|
|
/// eliminate all such dead live ranges.
|
|
bool SemanticARCOptVisitor::eliminateDeadLiveRangeCopyValue(CopyValueInst *cvi) {
|
|
// See if we are lucky and have a simple case.
|
|
if (auto *op = cvi->getSingleUse()) {
|
|
if (auto *dvi = dyn_cast<DestroyValueInst>(op->getUser())) {
|
|
eraseInstruction(dvi);
|
|
eraseInstructionAndAddOperandsToWorklist(cvi);
|
|
NumEliminatedInsts += 2;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// If all of our copy_value users are destroy_value, zap all of the
|
|
// instructions. We begin by performing that check and gathering up our
|
|
// destroy_value.
|
|
SmallVector<DestroyValueInst *, 16> destroys;
|
|
if (!all_of(cvi->getUses(), [&](Operand *op) {
|
|
auto *dvi = dyn_cast<DestroyValueInst>(op->getUser());
|
|
if (!dvi)
|
|
return false;
|
|
|
|
// Stash dvi in destroys so we can easily eliminate it later.
|
|
destroys.push_back(dvi);
|
|
return true;
|
|
})) {
|
|
return false;
|
|
}
|
|
|
|
// Now that we have a truly dead live range copy value, eliminate it!
|
|
while (!destroys.empty()) {
|
|
eraseInstruction(destroys.pop_back_val());
|
|
++NumEliminatedInsts;
|
|
}
|
|
eraseInstructionAndAddOperandsToWorklist(cvi);
|
|
++NumEliminatedInsts;
|
|
return true;
|
|
}
|
|
|
|
// Handle simple checking where we do not need to form live ranges and visit a
|
|
// bunch of instructions.
|
|
static bool canSafelyJoinSimpleRange(SILValue cviOperand,
|
|
DestroyValueInst *cviOperandDestroy,
|
|
CopyValueInst *cvi) {
|
|
// We only handle cases where our copy_value has a single consuming use that
|
|
// is not a forwarding use. We need to use the LiveRange functionality to
|
|
// guarantee correctness in the presence of forwarding uses.
|
|
//
|
|
// NOTE: This use may be any type of consuming use and may not be a
|
|
// destroy_value.
|
|
auto *cviConsumer = cvi->getSingleConsumingUse();
|
|
if (!cviConsumer || isOwnedForwardingInstruction(cviConsumer->getUser())) {
|
|
return false;
|
|
}
|
|
|
|
// Ok, we may be able to eliminate this. The main thing we need to be careful
|
|
// of here is that if the destroy_value is /after/ the consuming use of the
|
|
// operand of copy_value, we may have normal uses of the copy_value's operand
|
|
// that would become use-after-frees since we would be shrinking the lifetime
|
|
// of the object potentially. Consider the following SIL:
|
|
//
|
|
// %0 = ...
|
|
// %1 = copy_value %0
|
|
// apply %cviConsumer(%1)
|
|
// apply %guaranteedUser(%0)
|
|
// destroy_value %0
|
|
//
|
|
// Easily, if we were to eliminate the copy_value, destroy_value, the object's
|
|
// lifetime could potentially be shrunk before guaranteedUser is executed,
|
|
// causing guaranteedUser to be a use-after-free.
|
|
//
|
|
// As an extra wrinkle, until all interior pointer constructs (e.x.:
|
|
// project_box) are guaranteed to be guaranted by a begin_borrow, we can not
|
|
// in general safely shrink lifetimes. So even if we think we can prove that
|
|
// all non-consuming uses of %0 are before apply %cviConsumer, we may miss
|
|
// implicit uses that are not guarded yet by a begin_borrow, resulting in
|
|
// use-after-frees.
|
|
//
|
|
// With that in mind, we only handle cases today where we can prove that
|
|
// destroy_value is strictly before the consuming use of the operand. This
|
|
// guarantees that we are not shrinking the lifetime of the underlying object.
|
|
//
|
|
// First we handle the simple case: where the cviConsumer is a return inst. In
|
|
// such a case, we know for sure that cviConsumer post-dominates the
|
|
// destroy_value.
|
|
auto cviConsumerIter = cviConsumer->getUser()->getIterator();
|
|
if (isa<ReturnInst>(cviConsumerIter)) {
|
|
return true;
|
|
}
|
|
|
|
// Then see if our cviConsumer is in the same block as a return inst and the
|
|
// destroy_value is not. In that case, we know that the cviConsumer must
|
|
// post-dominate the destroy_value.
|
|
auto *cviConsumingBlock = cviConsumerIter->getParent();
|
|
if (isa<ReturnInst>(cviConsumingBlock->getTerminator()) &&
|
|
cviConsumingBlock != cviOperandDestroy->getParent()) {
|
|
return true;
|
|
}
|
|
|
|
// Otherwise, we only support joining live ranges where the cvi and the cvi's
|
|
// operand's destroy are in the same block with the destroy_value of cvi
|
|
// operand needing to be strictly after the copy_value. This analysis can be
|
|
// made significantly stronger by using LiveRanges, but this is simple for
|
|
// now.
|
|
auto cviOperandDestroyIter = cviOperandDestroy->getIterator();
|
|
if (cviConsumingBlock != cviOperandDestroyIter->getParent()) {
|
|
return false;
|
|
}
|
|
|
|
// TODO: This should really be llvm::find, but for some reason, the templates
|
|
// do not match up given the current state of the iterators. This impl works
|
|
// in a pinch though.
|
|
return llvm::any_of(
|
|
llvm::make_range(cviOperandDestroyIter,
|
|
cviOperandDestroyIter->getParent()->end()),
|
|
[&](const SILInstruction &val) { return &*cviConsumerIter == &val; });
|
|
}
|
|
|
|
// # The Problem We Are Solving
|
|
//
|
|
// The main idea here is that we are trying to eliminate the simplest, easiest
|
|
// form of live range joining. Consider the following SIL:
|
|
//
|
|
// ```
|
|
// %cviOperand = ... // @owned value
|
|
// %cvi = copy_value %cviOperand // copy of @owned value
|
|
// ...
|
|
// destroy_value %cviOperandDestroy // destruction of @owned value
|
|
// ...
|
|
// apply %consumingUser(%cvi) // destruction of copy of @owned value
|
|
// ```
|
|
//
|
|
// We want to reduce reference count traffic by eliminating the middle
|
|
// copy/destroy yielding:
|
|
//
|
|
// ```
|
|
// %cviOperand = ... // @owned value
|
|
// // *eliminated copy_value*
|
|
// ...
|
|
// // *eliminated destroy_value*
|
|
// ...
|
|
// apply %consumingUser(%cviOperand) // destruction of copy of @owned
|
|
// value
|
|
// ```
|
|
//
|
|
// # Safety
|
|
//
|
|
// In order to do this safely, we need to take the union of the two objects
|
|
// lifetimes since we are only joining lifetimes. This ensures that we can rely
|
|
// on SILGen's correctness on inserting safe lifetimes. To keep this simple
|
|
// today we only optimize if the destroy_value and consuming user are in the
|
|
// same block and the consuming user is later in the block than the
|
|
// destroy_value.
|
|
//
|
|
// DISCUSSION: The reason why we do not shrink lifetimes today is that all
|
|
// interior pointers (e.x. project_box) are properly guarded by
|
|
// begin_borrow. Because of that we can not shrink lifetimes and instead rely on
|
|
// SILGen's correctness.
|
|
bool SemanticARCOptVisitor::tryJoiningCopyValueLiveRangeWithOperand(
|
|
CopyValueInst *cvi) {
|
|
// First do a quick check if our operand is owned. If it is not owned, we can
|
|
// not join live ranges.
|
|
SILValue operand = cvi->getOperand();
|
|
if (operand.getOwnershipKind() != ValueOwnershipKind::Owned) {
|
|
return false;
|
|
}
|
|
|
|
// Then check if our operand has a single destroy_value. If it does and that
|
|
// destroy_value is strictly before the consumer of our copy_value in the same
|
|
// block as the consumer of said copy_value then we can always join the live
|
|
// ranges.
|
|
//
|
|
// Example:
|
|
//
|
|
// ```
|
|
// %1 = copy_value %0
|
|
// ...
|
|
// destroy_value %0
|
|
// apply %consumingUser(%1)
|
|
// ```
|
|
// ->
|
|
//
|
|
// ```
|
|
// apply %consumingUser(%0)
|
|
// ```
|
|
//
|
|
// DISCUSSION: We need to ensure that the consuming use of the copy_value is
|
|
// strictly after the destroy_value to ensure that we do not shrink the live
|
|
// range of the operand if the operand has any normal uses beyond our copy
|
|
// value. Otherwise, we could have normal uses /after/ the consuming use of
|
|
// our copy_value.
|
|
if (auto *dvi = operand->getSingleConsumingUserOfType<DestroyValueInst>()) {
|
|
if (canSafelyJoinSimpleRange(operand, dvi, cvi)) {
|
|
eraseInstruction(dvi);
|
|
eraseAndRAUWSingleValueInstruction(cvi, operand);
|
|
NumEliminatedInsts += 2;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Otherwise, we couldn't handle this case, so return false.
|
|
return false;
|
|
}
|
|
|
|
bool SemanticARCOptVisitor::visitCopyValueInst(CopyValueInst *cvi) {
|
|
// If our copy value inst has only destroy_value users, it is a dead live
|
|
// range. Try to eliminate them.
|
|
if (eliminateDeadLiveRangeCopyValue(cvi)) {
|
|
return true;
|
|
}
|
|
|
|
// Then see if copy_value operand's lifetime ends after our copy_value via a
|
|
// destroy_value. If so, we can join their lifetimes.
|
|
if (tryJoiningCopyValueLiveRangeWithOperand(cvi)) {
|
|
return true;
|
|
}
|
|
|
|
// Then try to perform the guaranteed copy value optimization.
|
|
if (performGuaranteedCopyValueOptimization(cvi)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// load [copy] Optimizations
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
/// A class that computes in a flow insensitive way if we can prove that our
|
|
/// storage is either never written to, or is initialized exactly once and never
|
|
/// written to again. In both cases, we can convert load [copy] -> load_borrow
|
|
/// safely.
|
|
class StorageGuaranteesLoadVisitor
|
|
: public AccessUseDefChainVisitor<StorageGuaranteesLoadVisitor>
|
|
{
|
|
// The outer SemanticARCOptVisitor.
|
|
SemanticARCOptVisitor &ARCOpt;
|
|
|
|
// The live range of the original load.
|
|
const LiveRange &liveRange;
|
|
|
|
// The current address being visited.
|
|
SILValue currentAddress;
|
|
|
|
Optional<bool> isWritten;
|
|
|
|
public:
|
|
StorageGuaranteesLoadVisitor(SemanticARCOptVisitor &arcOpt, LoadInst *load,
|
|
const LiveRange &liveRange)
|
|
: ARCOpt(arcOpt), liveRange(liveRange),
|
|
currentAddress(load->getOperand()) {}
|
|
|
|
void answer(bool written) {
|
|
currentAddress = nullptr;
|
|
isWritten = written;
|
|
}
|
|
|
|
void next(SILValue address) {
|
|
currentAddress = address;
|
|
}
|
|
|
|
void visitNestedAccess(BeginAccessInst *access) {
|
|
// First see if we have read/modify. If we do not, just look through the
|
|
// nested access.
|
|
switch (access->getAccessKind()) {
|
|
case SILAccessKind::Init:
|
|
case SILAccessKind::Deinit:
|
|
return next(access->getOperand());
|
|
case SILAccessKind::Read:
|
|
case SILAccessKind::Modify:
|
|
break;
|
|
}
|
|
|
|
// Next check if our live range is completely in the begin/end access
|
|
// scope. If so, we may be able to use a load_borrow here!
|
|
SmallVector<Operand *, 8> endScopeUses;
|
|
transform(access->getEndAccesses(), std::back_inserter(endScopeUses),
|
|
[](EndAccessInst *eai) {
|
|
return &eai->getAllOperands()[0];
|
|
});
|
|
SmallPtrSet<SILBasicBlock *, 4> visitedBlocks;
|
|
LinearLifetimeChecker checker(visitedBlocks, ARCOpt.getDeadEndBlocks());
|
|
if (!checker.validateLifetime(access, endScopeUses,
|
|
liveRange.getDestroyingUses())) {
|
|
// If we fail the linear lifetime check, then just recur:
|
|
return next(access->getOperand());
|
|
}
|
|
|
|
// Otherwise, if we have read, then we are done!
|
|
if (access->getAccessKind() == SILAccessKind::Read) {
|
|
return answer(false);
|
|
}
|
|
|
|
// If we have a modify, check if our value is /ever/ written to. If it is
|
|
// never actually written to, then we convert to a load_borrow.
|
|
return answer(ARCOpt.isAddressWrittenToDefUseAnalysis(access));
|
|
}
|
|
|
|
void visitArgumentAccess(SILFunctionArgument *arg) {
|
|
// If this load_copy is from an indirect in_guaranteed argument, then we
|
|
// know for sure that it will never be written to.
|
|
if (arg->hasConvention(SILArgumentConvention::Indirect_In_Guaranteed)) {
|
|
return answer(false);
|
|
}
|
|
|
|
// If we have an inout parameter that isn't ever actually written to, return
|
|
// false.
|
|
if (arg->getKnownParameterInfo().isIndirectMutating()) {
|
|
return answer(ARCOpt.isAddressWrittenToDefUseAnalysis(arg));
|
|
}
|
|
|
|
// TODO: This should be extended:
|
|
//
|
|
// 1. We should be able to analyze in arguments and see if they are only
|
|
// ever destroyed at the end of the function. In such a case, we may be
|
|
// able to also to promote load [copy] from such args to load_borrow.
|
|
return answer(true);
|
|
}
|
|
|
|
void visitGlobalAccess(SILValue global) {
|
|
return answer(!AccessedStorage(global, AccessedStorage::Global)
|
|
.isLetAccess(&ARCOpt.F));
|
|
}
|
|
|
|
void visitClassAccess(RefElementAddrInst *field) {
|
|
currentAddress = nullptr;
|
|
|
|
// We know a let property won't be written to if the base object is
|
|
// guaranteed for the duration of the access.
|
|
// For non-let properties conservatively assume they may be written to.
|
|
if (!field->getField()->isLet()) {
|
|
return answer(true);
|
|
}
|
|
|
|
// The lifetime of the `let` is guaranteed if it's dominated by the
|
|
// guarantee on the base. See if we can find a single borrow introducer for
|
|
// this object. If we could not find a single such borrow introducer, assume
|
|
// that our property is conservatively written to.
|
|
SILValue baseObject = field->getOperand();
|
|
auto value = getSingleBorrowIntroducingValue(baseObject);
|
|
if (!value) {
|
|
return answer(true);
|
|
}
|
|
|
|
// Ok, we have a single borrow introducing value. First do a quick check if
|
|
// we have a non-local scope that is a function argument. In such a case, we
|
|
// know statically that our let can not be written to in the current
|
|
// function. To be conservative, assume that all other non-local scopes
|
|
// write to memory.
|
|
if (!value->isLocalScope()) {
|
|
if (value->kind == BorrowScopeIntroducingValueKind::SILFunctionArgument) {
|
|
return answer(false);
|
|
}
|
|
|
|
// TODO: Once we model Coroutine results as non-local scopes, we should be
|
|
// able to return false here for them as well.
|
|
return answer(true);
|
|
}
|
|
|
|
// TODO: This is disabled temporarily for guaranteed phi args just for
|
|
// staging purposes. Thus be conservative and assume true in these cases.
|
|
if (value->kind == BorrowScopeIntroducingValueKind::Phi) {
|
|
return answer(true);
|
|
}
|
|
|
|
// Ok, we now know that we have a local scope whose lifetime we need to
|
|
// analyze. With that in mind, gather up the lifetime ending uses of our
|
|
// borrow scope introducing value and then use the linear lifetime checker
|
|
// to check whether the copied value is dominated by the lifetime of the
|
|
// borrow it's based on.
|
|
SmallVector<Operand *, 4> endScopeInsts;
|
|
value->visitLocalScopeEndingUses(
|
|
[&](Operand *use) { endScopeInsts.push_back(use); });
|
|
|
|
SmallPtrSet<SILBasicBlock *, 4> visitedBlocks;
|
|
LinearLifetimeChecker checker(visitedBlocks, ARCOpt.getDeadEndBlocks());
|
|
|
|
// Returns true on success. So we invert.
|
|
bool foundError = !checker.validateLifetime(baseObject, endScopeInsts,
|
|
liveRange.getDestroyingUses());
|
|
return answer(foundError);
|
|
}
|
|
|
|
// TODO: Handle other access kinds?
|
|
void visitBase(SILValue base, AccessedStorage::Kind kind) {
|
|
return answer(true);
|
|
}
|
|
|
|
void visitNonAccess(SILValue addr) {
|
|
return answer(true);
|
|
}
|
|
|
|
void visitIncomplete(SILValue projectedAddr, SILValue parentAddr) {
|
|
return next(parentAddr);
|
|
}
|
|
|
|
void visitPhi(SILPhiArgument *phi) {
|
|
// We shouldn't have address phis in OSSA SIL, so we don't need to recur
|
|
// through the predecessors here.
|
|
return answer(true);
|
|
}
|
|
|
|
/// See if we have an alloc_stack that is only written to once by an
|
|
/// initializing instruction.
|
|
void visitStackAccess(AllocStackInst *stack) {
|
|
SmallVector<Operand *, 8> destroyAddrOperands;
|
|
bool initialAnswer = isSingleInitAllocStack(stack, destroyAddrOperands);
|
|
if (!initialAnswer)
|
|
return answer(true);
|
|
|
|
// Then make sure that all of our load [copy] uses are within the
|
|
// destroy_addr.
|
|
SmallPtrSet<SILBasicBlock *, 4> visitedBlocks;
|
|
LinearLifetimeChecker checker(visitedBlocks, ARCOpt.getDeadEndBlocks());
|
|
// Returns true on success. So we invert.
|
|
bool foundError = !checker.validateLifetime(
|
|
stack, destroyAddrOperands /*consuming users*/,
|
|
liveRange.getDestroyingUses() /*non consuming users*/);
|
|
return answer(foundError);
|
|
}
|
|
|
|
bool doIt() {
|
|
while (currentAddress) {
|
|
visit(currentAddress);
|
|
}
|
|
return *isWritten;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
bool SemanticARCOptVisitor::isWrittenTo(LoadInst *load, const LiveRange &lr) {
|
|
StorageGuaranteesLoadVisitor visitor(*this, load, lr);
|
|
return visitor.doIt();
|
|
}
|
|
|
|
// Convert a load [copy] from unique storage [read] that has all uses that can
|
|
// accept a guaranteed parameter to a load_borrow.
|
|
bool SemanticARCOptVisitor::visitLoadInst(LoadInst *li) {
|
|
if (li->getOwnershipQualifier() != LoadOwnershipQualifier::Copy)
|
|
return false;
|
|
|
|
// Ok, we have our load [copy]. Make sure its value is truly a dead live range
|
|
// implying it is only ever consumed by destroy_value instructions. If it is
|
|
// consumed, we need to pass off a +1 value, so bail.
|
|
//
|
|
// FIXME: We should consider if it is worth promoting a load [copy]
|
|
// -> load_borrow if we can put a copy_value on a cold path and thus
|
|
// eliminate RR traffic on a hot path.
|
|
LiveRange lr(li);
|
|
if (lr.hasConsumingUse())
|
|
return false;
|
|
|
|
// Then check if our address is ever written to. If it is, then we cannot use
|
|
// the load_borrow because the stored value may be released during the loaded
|
|
// value's live range.
|
|
if (isWrittenTo(li, lr))
|
|
return false;
|
|
|
|
// Ok, we can perform our optimization. Convert the load [copy] into a
|
|
// load_borrow.
|
|
auto *lbi =
|
|
SILBuilderWithScope(li).createLoadBorrow(li->getLoc(), li->getOperand());
|
|
|
|
// Since we are looking through forwarding uses that can accept guaranteed
|
|
// parameters, we can have multiple destroy_value along the same path. We need
|
|
// to find the post-dominating block set of these destroy value to ensure that
|
|
// we do not insert multiple end_borrow.
|
|
assert(lifetimeFrontier.empty());
|
|
ValueLifetimeAnalysis analysis(li, lr.getDestroyingInsts());
|
|
bool foundCriticalEdges = !analysis.computeFrontier(
|
|
lifetimeFrontier, ValueLifetimeAnalysis::DontModifyCFG,
|
|
&getDeadEndBlocks());
|
|
(void)foundCriticalEdges;
|
|
assert(!foundCriticalEdges);
|
|
auto loc = RegularLocation::getAutoGeneratedLocation();
|
|
while (!lifetimeFrontier.empty()) {
|
|
auto *insertPoint = lifetimeFrontier.pop_back_val();
|
|
SILBuilderWithScope builder(insertPoint);
|
|
builder.createEndBorrow(loc, lbi);
|
|
}
|
|
|
|
// Then delete all of our destroy_value.
|
|
auto destroyValues = lr.getDestroyingUses();
|
|
while (!destroyValues.empty()) {
|
|
auto *dvi = destroyValues.back();
|
|
destroyValues = destroyValues.drop_back();
|
|
eraseInstruction(dvi->getUser());
|
|
++NumEliminatedInsts;
|
|
}
|
|
|
|
// RAUW our other uses from the load to the load_borrow.
|
|
eraseAndRAUWSingleValueInstruction(li, lbi);
|
|
|
|
// And then change the ownership all of our owned forwarding users to be
|
|
// guaranteed.
|
|
convertForwardingUsesFromOwnedToGuaranteed(
|
|
lr.getNonConsumingForwardingUses());
|
|
|
|
++NumEliminatedInsts;
|
|
++NumLoadCopyConvertedToLoadBorrow;
|
|
return true;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Top Level Entrypoint
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
// Even though this is a mandatory pass, it is rerun after deserialization in
|
|
// case DiagnosticConstantPropagation exposed anything new in this assert
|
|
// configuration.
|
|
struct SemanticARCOpts : SILFunctionTransform {
|
|
void run() override {
|
|
SILFunction &f = *getFunction();
|
|
|
|
// Return early if we are not performing OSSA optimizations.
|
|
if (!f.getModule().getOptions().EnableOSSAOptimizations)
|
|
return;
|
|
|
|
// Make sure we are running with ownership verification enabled.
|
|
assert(f.getModule().getOptions().VerifySILOwnership &&
|
|
"Can not perform semantic arc optimization unless ownership "
|
|
"verification is enabled");
|
|
|
|
SemanticARCOptVisitor visitor(f);
|
|
|
|
// Add all the results of all instructions that we want to visit to the
|
|
// worklist.
|
|
for (auto &block : f) {
|
|
for (auto &inst : block) {
|
|
if (SemanticARCOptVisitor::shouldVisitInst(&inst)) {
|
|
for (SILValue v : inst.getResults()) {
|
|
visitor.worklist.insert(v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then process the worklist. We only destroy instructions, so invalidate
|
|
// that. Once we modify the ownership of block arguments, we will need to
|
|
// perhaps invalidate branches as well.
|
|
if (visitor.processWorklist()) {
|
|
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
|
|
}
|
|
}
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
SILTransform *swift::createSemanticARCOpts() { return new SemanticARCOpts(); }
|