mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Specifically, copyAndExtendForLifetimeEndingRAUW -> createPlusOneCopy(value, oldConsumingUse) copyAndExtendForNonLifetimeEndingRAUW -> createPlusZeroCopy(value, nonLifetimeEndingUses) copyBorrowAndExtendForRAUW -> createPlusZeroBorrow(value, nonLifetimeEndingUses) Beyond being shorter, the PlusOne vs PlusZero mnemonic is telling the caller whether or not the caller is responsible for cleaning up the returned copy_value. In the PlusZero cases, we assume that all uses are non-lifetime ending and we need to insert all destroy_value at the liveness points of the value. In the PlusOne case in contrast, we are supposed to make a copy of value and the complete oldConsumingUse's block into a joint post-dominance set of blocks. Then we insert destroy_values for our new copy into the completion blocks that we found and leave cleaning up value along paths through oldConsumingUse to our user. In this sense the value is a PlusOne value that our user must clean up for us (sorta makes me think about ManagedValue).
749 lines
30 KiB
C++
749 lines
30 KiB
C++
//===--- OwnershipOptUtils.cpp --------------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
///
|
|
/// Ownership Utilities that rely on SILOptimizer functionality.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/SILOptimizer/Utils/OwnershipOptUtils.h"
|
|
|
|
#include "swift/Basic/Defer.h"
|
|
#include "swift/SIL/BasicBlockUtils.h"
|
|
#include "swift/SIL/InstructionUtils.h"
|
|
#include "swift/SIL/LinearLifetimeChecker.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/SILOptimizer/Utils/CFGOptUtils.h"
|
|
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
|
|
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
|
|
|
|
using namespace swift;
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Utility Helper Functions
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static void cleanupOperandsBeforeDeletion(SILInstruction *oldValue,
|
|
InstModCallbacks &callbacks) {
|
|
SILBuilderWithScope builder(oldValue);
|
|
for (auto &op : oldValue->getAllOperands()) {
|
|
if (!op.isLifetimeEnding()) {
|
|
continue;
|
|
}
|
|
|
|
switch (op.get().getOwnershipKind()) {
|
|
case OwnershipKind::Any:
|
|
llvm_unreachable("Invalid ownership for value");
|
|
case OwnershipKind::Owned: {
|
|
auto *dvi = builder.createDestroyValue(oldValue->getLoc(), op.get());
|
|
callbacks.createdNewInst(dvi);
|
|
continue;
|
|
}
|
|
case OwnershipKind::Guaranteed: {
|
|
// Should only happen once we model destructures as true reborrows.
|
|
auto *ebi = builder.createEndBorrow(oldValue->getLoc(), op.get());
|
|
callbacks.createdNewInst(ebi);
|
|
continue;
|
|
}
|
|
case OwnershipKind::None:
|
|
continue;
|
|
case OwnershipKind::Unowned:
|
|
llvm_unreachable("Unowned object can never be consumed?!");
|
|
}
|
|
llvm_unreachable("Covered switch isn't covered");
|
|
}
|
|
}
|
|
|
|
static SILPhiArgument *
|
|
insertOwnedBaseValueAlongBranchEdge(BranchInst *bi, SILValue innerCopy,
|
|
InstModCallbacks &callbacks) {
|
|
auto *destBB = bi->getDestBB();
|
|
// We need to create the phi argument before calling addNewEdgeValueToBranch
|
|
// since it checks that the destination block has enough arguments for the
|
|
// argument.
|
|
auto *phiArg =
|
|
destBB->createPhiArgument(innerCopy->getType(), OwnershipKind::Owned);
|
|
addNewEdgeValueToBranch(bi, destBB, innerCopy, callbacks);
|
|
|
|
// Grab our predecessor blocks, ignoring us, add to the branch edge an
|
|
// undef corresponding to our value.
|
|
//
|
|
// We gather all predecessor blocks in a separate array to avoid
|
|
// iterator invalidation issues as we mess with terminators.
|
|
SmallVector<SILBasicBlock *, 8> predecessorBlocks(
|
|
destBB->getPredecessorBlocks());
|
|
|
|
for (auto *predBlock : predecessorBlocks) {
|
|
if (predBlock == innerCopy->getParentBlock())
|
|
continue;
|
|
addNewEdgeValueToBranch(
|
|
predBlock->getTerminator(), destBB,
|
|
SILUndef::get(innerCopy->getType(), *destBB->getParent()), callbacks);
|
|
}
|
|
|
|
return phiArg;
|
|
}
|
|
|
|
static void getAllNonTrivialUsePointsOfBorrowedValue(
|
|
SILValue value, SmallVectorImpl<Operand *> &usePoints,
|
|
SmallVectorImpl<BorrowingOperand> &reborrowPoints) {
|
|
assert(value.getOwnershipKind() == OwnershipKind::Guaranteed);
|
|
|
|
unsigned firstOffset = usePoints.size();
|
|
llvm::copy(value->getUses(), std::back_inserter(usePoints));
|
|
|
|
if (usePoints.size() == firstOffset)
|
|
return;
|
|
|
|
// NOTE: Use points resizes in this loop so usePoints.size() may be
|
|
// different every time.
|
|
for (unsigned i = firstOffset; i < usePoints.size(); ++i) {
|
|
if (auto fOperand = ForwardingOperand::get(usePoints[i])) {
|
|
fOperand->visitForwardedValues([&](SILValue transitiveValue) {
|
|
// Do not include transitive uses with 'none' ownership
|
|
if (transitiveValue.getOwnershipKind() == OwnershipKind::None)
|
|
return true;
|
|
for (auto *transitiveUse : transitiveValue->getUses())
|
|
usePoints.push_back(transitiveUse);
|
|
return true;
|
|
});
|
|
continue;
|
|
}
|
|
|
|
if (auto borrowingOp = BorrowingOperand::get(usePoints[i])) {
|
|
// If we have a reborrow, we have no further work to do, our reborrow is
|
|
// already a use and we will handle the reborrow separately.
|
|
if (borrowingOp->isReborrow())
|
|
continue;
|
|
|
|
// Otherwise, try to grab additional end scope instructions to find more
|
|
// liveness info. Stash any reborrow uses so that we can eliminate the
|
|
// reborrow before we are done processing.
|
|
borrowingOp->visitLocalEndScopeUses([&](Operand *scopeEndingUse) {
|
|
if (auto scopeEndingBorrowingOp =
|
|
BorrowingOperand::get(scopeEndingUse)) {
|
|
if (scopeEndingBorrowingOp->isReborrow()) {
|
|
reborrowPoints.push_back(scopeEndingUse);
|
|
return true;
|
|
}
|
|
}
|
|
usePoints.push_back(scopeEndingUse);
|
|
return true;
|
|
});
|
|
|
|
// Now break up all of the reborrows
|
|
continue;
|
|
}
|
|
|
|
// If our base guaranteed value does not have any consuming uses (consider
|
|
// function arguments), we need to be sure to include interior pointer
|
|
// operands since we may not get a use from a end_scope instruction.
|
|
if (auto intPtrOperand = InteriorPointerOperand::get(usePoints[i])) {
|
|
intPtrOperand->getImplicitUses(usePoints);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Ownership Lifetime Extender
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
struct OwnershipLifetimeExtender {
|
|
OwnershipFixupContext &ctx;
|
|
|
|
/// Create a new copy of \p value assuming that our caller will clean up the
|
|
/// copy along all paths that go through consuming point. Operationally this
|
|
/// means that the API will insert compensating destroy_value on the copy
|
|
/// along all paths that do not go through consuming point.
|
|
///
|
|
/// DISCUSSION: If \p consumingPoint is an instruction that forwards \p value,
|
|
/// calling this and then RAUWing with \p value guarantee that \p value will
|
|
/// be consumed by the forwarding instruction's results consuming uses.
|
|
CopyValueInst *createPlusOneCopy(SILValue value,
|
|
SILInstruction *consumingPoint);
|
|
|
|
/// Create a copy of \p value that covers all of \p range and insert all
|
|
/// needed destroy_values. We assume that no uses in \p range consume \p
|
|
/// value.
|
|
CopyValueInst *createPlusZeroCopy(SILValue value, ArrayRef<Operand *> range) {
|
|
return createPlusZeroCopy<ArrayRef<Operand *>>(value, range);
|
|
}
|
|
|
|
/// Create a copy of \p value that covers all of \p range and insert all
|
|
/// needed destroy_values. We assume that all uses in \p range do not consume
|
|
/// \p value.
|
|
///
|
|
/// We return our copy_value to the user at +0 to show that they do not need
|
|
/// to insert cleanup destroys.
|
|
template <typename RangeTy>
|
|
CopyValueInst *createPlusZeroCopy(SILValue value, const RangeTy &range);
|
|
|
|
/// Create a new borrow scope for \p newValue that contains all uses in \p
|
|
/// useRange. We assume that \p useRange does not contain any lifetime ending
|
|
/// uses.
|
|
template <typename RangeTy>
|
|
BeginBorrowInst *createPlusZeroBorrow(SILValue newValue, RangeTy useRange);
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
// Lifetime extend newValue over owned oldValue assuming that our copy will have
|
|
// its lifetime ended by oldValue's lifetime ending uses after RAUWing by our
|
|
// caller.
|
|
CopyValueInst *
|
|
OwnershipLifetimeExtender::createPlusOneCopy(SILValue value,
|
|
SILInstruction *consumingPoint) {
|
|
auto *newValInsertPt = value->getDefiningInsertionPoint();
|
|
assert(newValInsertPt);
|
|
CopyValueInst *copy;
|
|
if (!isa<SILArgument>(value)) {
|
|
SILBuilderWithScope::insertAfter(newValInsertPt, [&](SILBuilder &builder) {
|
|
copy = builder.createCopyValue(builder.getInsertionPointLoc(), value);
|
|
});
|
|
} else {
|
|
SILBuilderWithScope builder(newValInsertPt);
|
|
copy = builder.createCopyValue(newValInsertPt->getLoc(), value);
|
|
}
|
|
|
|
auto &callbacks = ctx.callbacks;
|
|
callbacks.createdNewInst(copy);
|
|
|
|
auto *result = copy;
|
|
ctx.jointPostDomSetComputer.findJointPostDominatingSet(
|
|
newValInsertPt->getParent(), consumingPoint->getParent(),
|
|
// inputBlocksFoundDuringWalk.
|
|
[&](SILBasicBlock *loopBlock) {
|
|
// This must be consumingPoint->getParent() since we only have one
|
|
// consuming use. In this case, we know that this is the consuming
|
|
// point where we will need a control equivalent copy_value (and that
|
|
// destroy_value will be put for the out of loop value as appropriate.
|
|
assert(loopBlock == consumingPoint->getParent());
|
|
auto front = loopBlock->begin();
|
|
SILBuilderWithScope newBuilder(front);
|
|
result = newBuilder.createCopyValue(front->getLoc(), copy);
|
|
callbacks.createdNewInst(result);
|
|
|
|
llvm_unreachable("Should never visit this!");
|
|
},
|
|
// Input blocks in joint post dom set. We don't care about thse.
|
|
[&](SILBasicBlock *postDomBlock) {
|
|
auto front = postDomBlock->begin();
|
|
SILBuilderWithScope newBuilder(front);
|
|
auto *dvi = newBuilder.createDestroyValue(front->getLoc(), copy);
|
|
callbacks.createdNewInst(dvi);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
// A copy_value that we lifetime extend with destroy_value over range. We assume
|
|
// all instructions passed into range do not consume value.
|
|
template <typename RangeTy>
|
|
CopyValueInst *
|
|
OwnershipLifetimeExtender::createPlusZeroCopy(SILValue value,
|
|
const RangeTy &range) {
|
|
auto *newValInsertPt = value->getDefiningInsertionPoint();
|
|
assert(newValInsertPt);
|
|
|
|
CopyValueInst *copy;
|
|
|
|
if (!isa<SILArgument>(value)) {
|
|
SILBuilderWithScope::insertAfter(newValInsertPt, [&](SILBuilder &builder) {
|
|
copy = builder.createCopyValue(builder.getInsertionPointLoc(), value);
|
|
});
|
|
} else {
|
|
SILBuilderWithScope builder(newValInsertPt);
|
|
copy = builder.createCopyValue(newValInsertPt->getLoc(), value);
|
|
}
|
|
|
|
auto &callbacks = ctx.callbacks;
|
|
callbacks.createdNewInst(copy);
|
|
|
|
auto opRange = makeUserRange(range);
|
|
ValueLifetimeAnalysis lifetimeAnalysis(copy, opRange);
|
|
ValueLifetimeAnalysis::Frontier frontier;
|
|
bool result = lifetimeAnalysis.computeFrontier(
|
|
frontier, ValueLifetimeAnalysis::DontModifyCFG, &ctx.deBlocks);
|
|
assert(result);
|
|
|
|
while (!frontier.empty()) {
|
|
auto *insertPt = frontier.pop_back_val();
|
|
SILBuilderWithScope frontierBuilder(insertPt);
|
|
auto *dvi = frontierBuilder.createDestroyValue(insertPt->getLoc(), copy);
|
|
callbacks.createdNewInst(dvi);
|
|
}
|
|
|
|
return copy;
|
|
}
|
|
|
|
template <typename RangeTy>
|
|
BeginBorrowInst *
|
|
OwnershipLifetimeExtender::createPlusZeroBorrow(SILValue newValue,
|
|
RangeTy useRange) {
|
|
auto *newValInsertPt = newValue->getDefiningInsertionPoint();
|
|
assert(newValInsertPt);
|
|
|
|
CopyValueInst *copy = nullptr;
|
|
BeginBorrowInst *borrow = nullptr;
|
|
if (!isa<SILArgument>(newValue)) {
|
|
SILBuilderWithScope::insertAfter(newValInsertPt, [&](SILBuilder &builder) {
|
|
auto loc = builder.getInsertionPointLoc();
|
|
copy = builder.createCopyValue(loc, newValue);
|
|
borrow = builder.createBeginBorrow(loc, copy);
|
|
});
|
|
} else {
|
|
SILBuilderWithScope builder(newValInsertPt);
|
|
auto loc = newValInsertPt->getLoc();
|
|
copy = builder.createCopyValue(loc, newValue);
|
|
borrow = builder.createBeginBorrow(loc, copy);
|
|
}
|
|
assert(copy && borrow);
|
|
|
|
auto opRange = makeUserRange(useRange);
|
|
ValueLifetimeAnalysis lifetimeAnalysis(copy, opRange);
|
|
ValueLifetimeAnalysis::Frontier frontier;
|
|
bool result = lifetimeAnalysis.computeFrontier(
|
|
frontier, ValueLifetimeAnalysis::DontModifyCFG, &ctx.deBlocks);
|
|
assert(result);
|
|
|
|
auto &callbacks = ctx.callbacks;
|
|
while (!frontier.empty()) {
|
|
auto *insertPt = frontier.pop_back_val();
|
|
SILBuilderWithScope frontierBuilder(insertPt);
|
|
// Use an auto-generated location here, because insertPt may have an
|
|
// incompatible LocationKind
|
|
auto loc = RegularLocation::getAutoGeneratedLocation(
|
|
insertPt->getLoc().getSourceLoc());
|
|
auto *ebi = frontierBuilder.createEndBorrow(loc, borrow);
|
|
auto *dvi = frontierBuilder.createDestroyValue(loc, copy);
|
|
callbacks.createdNewInst(ebi);
|
|
callbacks.createdNewInst(dvi);
|
|
}
|
|
|
|
return borrow;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Reborrow Elimination
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static void eliminateReborrowsOfRecursiveBorrows(
|
|
ArrayRef<BorrowingOperand> transitiveReborrows,
|
|
SmallVectorImpl<Operand *> &usePoints, InstModCallbacks &callbacks) {
|
|
SmallVector<std::pair<SILPhiArgument *, SILPhiArgument *>, 8>
|
|
baseBorrowedValuePair;
|
|
// Ok, we have transitive reborrows.
|
|
for (auto borrowingOperand : transitiveReborrows) {
|
|
// We eliminate the reborrow by creating a new copy+borrow at the reborrow
|
|
// edge from the base value and using that for the reborrow instead of the
|
|
// actual value. We of course insert an end_borrow for our original incoming
|
|
// value.
|
|
SILValue value = borrowingOperand->get();
|
|
auto *bi = cast<BranchInst>(borrowingOperand->getUser());
|
|
SILBuilderWithScope reborrowBuilder(bi);
|
|
// Use an auto-generated location here, because the branch may have an
|
|
// incompatible LocationKind
|
|
auto loc =
|
|
RegularLocation::getAutoGeneratedLocation(bi->getLoc().getSourceLoc());
|
|
auto *innerCopy = reborrowBuilder.createCopyValue(loc, value);
|
|
auto *innerBorrow = reborrowBuilder.createBeginBorrow(loc, innerCopy);
|
|
auto *outerEndBorrow = reborrowBuilder.createEndBorrow(loc, value);
|
|
|
|
callbacks.createdNewInst(innerCopy);
|
|
callbacks.createdNewInst(innerBorrow);
|
|
callbacks.createdNewInst(outerEndBorrow);
|
|
|
|
// Then set our borrowing operand to take our innerBorrow instead of value
|
|
// (whose lifetime we just ended).
|
|
callbacks.setUseValue(*borrowingOperand, innerBorrow);
|
|
// Add our outer end borrow as a use point to make sure that we extend our
|
|
// base value to this point.
|
|
usePoints.push_back(&outerEndBorrow->getAllOperands()[0]);
|
|
|
|
// Then check if in our destination block, we have further reborrows. If we
|
|
// do, we need to recursively process them.
|
|
auto *borrowedArg =
|
|
const_cast<SILPhiArgument *>(bi->getArgForOperand(borrowingOperand));
|
|
auto *baseArg =
|
|
insertOwnedBaseValueAlongBranchEdge(bi, innerCopy, callbacks);
|
|
baseBorrowedValuePair.emplace_back(baseArg, borrowedArg);
|
|
}
|
|
|
|
// Now recursively update all further reborrows...
|
|
while (!baseBorrowedValuePair.empty()) {
|
|
SILPhiArgument *baseArg;
|
|
SILPhiArgument *borrowedArg;
|
|
std::tie(baseArg, borrowedArg) = baseBorrowedValuePair.pop_back_val();
|
|
|
|
for (auto *use : borrowedArg->getConsumingUses()) {
|
|
// If our consuming use is an end of scope marker, we need to end
|
|
// the lifetime of our base arg.
|
|
if (isEndOfScopeMarker(use->getUser())) {
|
|
SILBuilderWithScope::insertAfter(use->getUser(), [&](SILBuilder &b) {
|
|
auto *dvi = b.createDestroyValue(b.getInsertionPointLoc(), baseArg);
|
|
callbacks.createdNewInst(dvi);
|
|
});
|
|
continue;
|
|
}
|
|
|
|
// Otherwise, we have a reborrow. For now our reborrows must be
|
|
// phis. Add our owned value as a new argument of that phi along our
|
|
// edge and undef along all other edges.
|
|
auto borrowingOp = *BorrowingOperand::get(use);
|
|
auto *brInst = cast<BranchInst>(borrowingOp.op->getUser());
|
|
auto *newBorrowedPhi = brInst->getArgForOperand(borrowingOp);
|
|
auto *newBasePhi =
|
|
insertOwnedBaseValueAlongBranchEdge(brInst, baseArg, callbacks);
|
|
baseBorrowedValuePair.emplace_back(newBasePhi, newBorrowedPhi);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void rewriteReborrows(SILValue newBorrowedValue,
|
|
ArrayRef<BorrowingOperand> foundReborrows,
|
|
InstModCallbacks &callbacks) {
|
|
// Each initial reborrow that we have is a use of oldValue, so we know
|
|
// that copy should be valid at the reborrow.
|
|
SmallVector<std::pair<SILPhiArgument *, SILPhiArgument *>, 8>
|
|
baseBorrowedValuePair;
|
|
for (auto reborrow : foundReborrows) {
|
|
auto *bi = cast<BranchInst>(reborrow.op->getUser());
|
|
SILBuilderWithScope reborrowBuilder(bi);
|
|
// Use an auto-generated location here, because the branch may have an
|
|
// incompatible LocationKind
|
|
auto loc =
|
|
RegularLocation::getAutoGeneratedLocation(bi->getLoc().getSourceLoc());
|
|
auto *innerCopy = reborrowBuilder.createCopyValue(loc, newBorrowedValue);
|
|
auto *innerBorrow = reborrowBuilder.createBeginBorrow(loc, innerCopy);
|
|
auto *outerEndBorrow =
|
|
reborrowBuilder.createEndBorrow(loc, reborrow.op->get());
|
|
callbacks.createdNewInst(innerCopy);
|
|
callbacks.createdNewInst(innerBorrow);
|
|
callbacks.createdNewInst(outerEndBorrow);
|
|
|
|
callbacks.setUseValue(*reborrow, innerBorrow);
|
|
|
|
auto *borrowedArg =
|
|
const_cast<SILPhiArgument *>(bi->getArgForOperand(reborrow.op));
|
|
auto *baseArg =
|
|
insertOwnedBaseValueAlongBranchEdge(bi, innerCopy, callbacks);
|
|
baseBorrowedValuePair.emplace_back(baseArg, borrowedArg);
|
|
}
|
|
|
|
// Now, follow through all chains of reborrows.
|
|
while (!baseBorrowedValuePair.empty()) {
|
|
SILPhiArgument *baseArg;
|
|
SILPhiArgument *borrowedArg;
|
|
std::tie(baseArg, borrowedArg) = baseBorrowedValuePair.pop_back_val();
|
|
|
|
for (auto *use : borrowedArg->getConsumingUses()) {
|
|
// If our consuming use is an end of scope marker, we need to end
|
|
// the lifetime of our base arg.
|
|
if (isEndOfScopeMarker(use->getUser())) {
|
|
SILBuilderWithScope::insertAfter(use->getUser(), [&](SILBuilder &b) {
|
|
auto *dvi = b.createDestroyValue(b.getInsertionPointLoc(), baseArg);
|
|
callbacks.createdNewInst(dvi);
|
|
});
|
|
continue;
|
|
}
|
|
|
|
// Otherwise, we have a reborrow. For now our reborrows must be
|
|
// phis. Add our owned value as a new argument of that phi along our
|
|
// edge and undef along all other edges.
|
|
auto borrowingOp = *BorrowingOperand::get(use);
|
|
auto *brInst = cast<BranchInst>(borrowingOp.op->getUser());
|
|
auto *newBorrowedPhi = brInst->getArgForOperand(borrowingOp);
|
|
auto *newBasePhi =
|
|
insertOwnedBaseValueAlongBranchEdge(brInst, baseArg, callbacks);
|
|
baseBorrowedValuePair.emplace_back(newBasePhi, newBorrowedPhi);
|
|
}
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Ownership Fixup RAUW
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Given an old value and a new value, lifetime extend new value as appropriate
|
|
/// so we can RAUW new value with old value and preserve ownership
|
|
/// invariants. We leave fixing up the lifetime of old value to our caller.
|
|
namespace {
|
|
|
|
struct OwnershipRAUWUtility {
|
|
SingleValueInstruction *oldValue;
|
|
SILValue newValue;
|
|
OwnershipFixupContext &ctx;
|
|
|
|
SILBasicBlock::iterator handleUnowned();
|
|
|
|
SILBasicBlock::iterator handleGuaranteed();
|
|
|
|
SILBasicBlock::iterator perform();
|
|
|
|
/// Insert copies/borrows as appropriate to eliminate any reborrows of
|
|
/// borrowed value, given we are going to replace it with newValue.
|
|
void eliminateReborrows(BorrowedValue oldBorrowedValue, SILValue newValue);
|
|
|
|
OwnershipLifetimeExtender getLifetimeExtender() { return {ctx}; }
|
|
|
|
const InstModCallbacks &getCallbacks() const { return ctx.callbacks; }
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
SILBasicBlock::iterator OwnershipRAUWUtility::handleUnowned() {
|
|
auto &callbacks = ctx.callbacks;
|
|
switch (newValue.getOwnershipKind()) {
|
|
case OwnershipKind::None:
|
|
llvm_unreachable("Should have been handled elsewhere");
|
|
case OwnershipKind::Any:
|
|
llvm_unreachable("Invalid for values");
|
|
case OwnershipKind::Unowned:
|
|
// An unowned value can always be RAUWed with another unowned value.
|
|
return replaceAllUsesAndErase(oldValue, newValue, callbacks);
|
|
case OwnershipKind::Guaranteed: {
|
|
// If we have an unowned value that we want to replace with a guaranteed
|
|
// value, we need to ensure that the guaranteed value is live at all use
|
|
// points of the unowned value. If so, just replace and continue.
|
|
//
|
|
// TODO: Implement this.
|
|
|
|
// Otherwise, we need to lifetime extend the borrow over all of the use
|
|
// points. To do so, we copy the value, borrow it, insert an unchecked
|
|
// ownership conversion to unowned at oldValue and then RAUW.
|
|
//
|
|
// We need to insert the conversion to ensure that we do not violate
|
|
// ownership propagation rules of forwarding insts.
|
|
SmallVector<Operand *, 8> oldValueUses(oldValue->getUses());
|
|
for (auto *use : oldValueUses) {
|
|
if (auto *ti = dyn_cast<TermInst>(use->getUser())) {
|
|
if (ti->isFunctionExiting()) {
|
|
SILBuilderWithScope builder(ti);
|
|
auto *newInst = builder.createUncheckedOwnershipConversion(
|
|
ti->getLoc(), use->get(), OwnershipKind::Unowned);
|
|
callbacks.createdNewInst(newInst);
|
|
callbacks.setUseValue(use, newInst);
|
|
}
|
|
}
|
|
}
|
|
auto extender = getLifetimeExtender();
|
|
SILValue borrow =
|
|
extender.createPlusZeroBorrow(newValue, oldValue->getUses());
|
|
SILBuilderWithScope builder(oldValue);
|
|
auto *newInst = builder.createUncheckedOwnershipConversion(
|
|
oldValue->getLoc(), borrow, OwnershipKind::Unowned);
|
|
callbacks.createdNewInst(newInst);
|
|
return replaceAllUsesAndErase(oldValue, newInst, callbacks);
|
|
}
|
|
case OwnershipKind::Owned: {
|
|
// If we have an unowned value that we want to replace with an owned value,
|
|
// we first check if the owned value is live over all use points of the old
|
|
// value. If so, just RAUW and continue.
|
|
//
|
|
// TODO: Implement this.
|
|
|
|
// Otherwise, insert a copy of the owned value and lifetime extend that over
|
|
// all uses of the value and then RAUW.
|
|
SmallVector<Operand *, 8> oldValueUses(oldValue->getUses());
|
|
for (auto *use : oldValueUses) {
|
|
if (auto *ti = dyn_cast<TermInst>(use->getUser())) {
|
|
if (ti->isFunctionExiting()) {
|
|
SILBuilderWithScope builder(ti);
|
|
// We insert this to ensure that we can extend our owned value's
|
|
// lifetime to before the function end point.
|
|
auto *newInst = builder.createUncheckedOwnershipConversion(
|
|
ti->getLoc(), use->get(), OwnershipKind::Unowned);
|
|
callbacks.createdNewInst(newInst);
|
|
callbacks.setUseValue(use, newInst);
|
|
}
|
|
}
|
|
}
|
|
auto extender = getLifetimeExtender();
|
|
SILValue copy = extender.createPlusZeroCopy(newValue, oldValue->getUses());
|
|
SILBuilderWithScope builder(oldValue);
|
|
auto *newInst = builder.createUncheckedOwnershipConversion(
|
|
oldValue->getLoc(), copy, OwnershipKind::Unowned);
|
|
callbacks.createdNewInst(newInst);
|
|
auto result = replaceAllUsesAndErase(oldValue, newInst, callbacks);
|
|
return result;
|
|
}
|
|
}
|
|
llvm_unreachable("covered switch isn't covered?!");
|
|
}
|
|
|
|
SILBasicBlock::iterator OwnershipRAUWUtility::handleGuaranteed() {
|
|
// If we want to replace a guaranteed value with a value of some other
|
|
// ownership whose def dominates our guaranteed value. We first see if all
|
|
// uses of the old guaranteed value are within the lifetime of the new
|
|
// guaranteed value. If so, we can just RAUW and move on.
|
|
//
|
|
// TODO: Implement this.
|
|
//
|
|
// Otherwise, we need to actually modify the IR. We first always first
|
|
// lifetime extend newValue to oldValue's transitive uses to set our
|
|
// workspace.
|
|
SmallVector<Operand *, 32> usePoints;
|
|
SmallVector<BorrowingOperand, 8> recursiveBorrowScopeReborrows;
|
|
getAllNonTrivialUsePointsOfBorrowedValue(oldValue, usePoints,
|
|
recursiveBorrowScopeReborrows);
|
|
|
|
// If we have any transitive reborrows on sub-borrows.
|
|
if (recursiveBorrowScopeReborrows.size())
|
|
eliminateReborrowsOfRecursiveBorrows(recursiveBorrowScopeReborrows,
|
|
usePoints, ctx.callbacks);
|
|
|
|
auto extender = getLifetimeExtender();
|
|
SILValue newBorrowedValue =
|
|
extender.createPlusZeroBorrow<ArrayRef<Operand *>>(newValue, usePoints);
|
|
|
|
// Now we need to handle reborrows by eliminating the reborrows from any
|
|
// borrowing operands that use old value as well as from oldvalue itself. We
|
|
// take advantage of a few properties of reborrows:
|
|
//
|
|
// 1. A reborrow has to be on a BorrowedValue. This ensures that the same
|
|
// base value is propagated through chains of reborrows. (In the future
|
|
// this may not be true when destructures are introduced as reborrow
|
|
// instructions).
|
|
//
|
|
// 2. Given that, we change each reborrows into new copy+borrow from the
|
|
// owned value that we perform at the reborrow use. What is nice about
|
|
// this formulation is that it ensures that we are always working with a
|
|
// non-dominating copy value, allowing us to force our borrowing value to
|
|
// need a base phi argument (the one of our choosing).
|
|
if (auto oldValueBorrowedVal = BorrowedValue::get(oldValue)) {
|
|
SmallVector<BorrowingOperand, 8> foundReborrows;
|
|
if (oldValueBorrowedVal->gatherReborrows(foundReborrows)) {
|
|
rewriteReborrows(newBorrowedValue, foundReborrows, ctx.callbacks);
|
|
}
|
|
}
|
|
|
|
// Then we need to look and see if our oldValue had any transitive uses that
|
|
|
|
// Ok, we now have eliminated any reborrows if we had any. That means that
|
|
// the uses of oldValue should be completely within the lifetime of our new
|
|
// borrow.
|
|
return replaceAllUsesAndErase(oldValue, newBorrowedValue, ctx.callbacks);
|
|
}
|
|
|
|
SILBasicBlock::iterator OwnershipRAUWUtility::perform() {
|
|
assert(oldValue->getFunction()->hasOwnership());
|
|
assert(
|
|
OwnershipFixupContext::canFixUpOwnershipForRAUW(oldValue, newValue) &&
|
|
"Should have checked if can perform this operation before calling it?!");
|
|
// If our new value is just none, we can pass anything to do it so just RAUW
|
|
// and return.
|
|
//
|
|
// NOTE: This handles RAUWing with undef.
|
|
if (newValue.getOwnershipKind() == OwnershipKind::None)
|
|
return replaceAllUsesAndErase(oldValue, newValue, ctx.callbacks);
|
|
assert(SILValue(oldValue).getOwnershipKind() != OwnershipKind::None);
|
|
|
|
switch (SILValue(oldValue).getOwnershipKind()) {
|
|
case OwnershipKind::None:
|
|
// If our old value was none and our new value is not, we need to do
|
|
// something more complex that we do not support yet, so bail. We should
|
|
// have not called this function in such a case.
|
|
llvm_unreachable("Should have been handled elsewhere");
|
|
case OwnershipKind::Any:
|
|
llvm_unreachable("Invalid for values");
|
|
case OwnershipKind::Guaranteed: {
|
|
return handleGuaranteed();
|
|
}
|
|
case OwnershipKind::Owned: {
|
|
// If we have an owned value that we want to replace with a value with any
|
|
// other non-None ownership, we need to copy the other value for a
|
|
// lifetimeEnding RAUW, then RAUW the value, and insert a destroy_value on
|
|
// the original value.
|
|
auto extender = getLifetimeExtender();
|
|
SILValue copy = extender.createPlusOneCopy(newValue, oldValue);
|
|
cleanupOperandsBeforeDeletion(oldValue, ctx.callbacks);
|
|
auto result = replaceAllUsesAndErase(oldValue, copy, ctx.callbacks);
|
|
return result;
|
|
}
|
|
case OwnershipKind::Unowned: {
|
|
return handleUnowned();
|
|
}
|
|
}
|
|
llvm_unreachable("Covered switch isn't covered?!");
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Ownership Fixup Context
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// All callers of our RAUW routines must ensure that their values return true
|
|
// from this.
|
|
bool OwnershipFixupContext::canFixUpOwnershipForRAUW(SILValue oldValue,
|
|
SILValue newValue) {
|
|
auto newOwnershipKind = newValue.getOwnershipKind();
|
|
|
|
// If our new kind is ValueOwnershipKind::None, then we are fine. We
|
|
// trivially support that. This check also ensures that we can always
|
|
// replace any value with a ValueOwnershipKind::None value.
|
|
if (newOwnershipKind == OwnershipKind::None)
|
|
return true;
|
|
|
|
// First check if oldValue is SILUndef. If it is, then we know that:
|
|
//
|
|
// 1. SILUndef (and thus oldValue) must have OwnershipKind::None.
|
|
// 2. newValue is not OwnershipKind::None due to our check above.
|
|
//
|
|
// Thus we know that we would be replacing a value with OwnershipKind::None
|
|
// with a value with non-None ownership. This is a case we don't support, so
|
|
// we can bail now.
|
|
if (isa<SILUndef>(oldValue))
|
|
return false;
|
|
|
|
// Ok, we now know that we do not have SILUndef implying that we must be able
|
|
// to get a module from our value since we must have an argument or an
|
|
// instruction.
|
|
auto *m = oldValue->getModule();
|
|
assert(m);
|
|
|
|
// If we are in Raw SIL, just bail at this point. We do not support
|
|
// ownership fixups.
|
|
if (m->getStage() == SILStage::Raw)
|
|
return false;
|
|
|
|
// If our old ownership kind is ValueOwnershipKind::None and our new kind is
|
|
// not, we may need to do more work that has not been implemented yet. So
|
|
// bail.
|
|
//
|
|
// Due to our requirement that types line up, this can only occur given a
|
|
// non-trivial typed value with None ownership. This can only happen when
|
|
// oldValue is a trivial payloaded or no-payload non-trivially typed
|
|
// enum. That doesn't occur that often so we just bail on it today until we
|
|
// implement this functionality.
|
|
auto oldOwnershipKind = SILValue(oldValue).getOwnershipKind();
|
|
if (oldOwnershipKind != OwnershipKind::None)
|
|
return true;
|
|
|
|
// Ok, we have an old ownership kind that is OwnershipKind::None and a new
|
|
// ownership kind that is not OwnershipKind::None. In that case, for now, do
|
|
// not perform this transform.
|
|
return false;
|
|
}
|
|
|
|
SILBasicBlock::iterator
|
|
OwnershipFixupContext::replaceAllUsesAndErase(SingleValueInstruction *oldValue,
|
|
SILValue newValue) {
|
|
OwnershipRAUWUtility utility{oldValue, newValue, *this};
|
|
return utility.perform();
|
|
}
|