[ownership] Add a new API to OwnershipFixupContext::replaceAllAddressUsesFixingInteriorPointerOwnership.

In OSSA, we enforce that addresses from interior pointer instructions are scoped
within a borrow scope. This means that it is invalid to use such an address
outside of its parent borrow scope and as a result one can not just RAUW an
address value by a dominating address value since the latter may be invalid at
the former. I foresee that I am going to have to solve this problem and so I
decided to write this API to handle the vast majority of cases.

The way this API works is that it:

1. Computes an access path with base for the new value. If we do not have a base
value and a valid access path with root, we bail.

2. Then we check if our base value is the result of an interior pointer
instruction. If it isn't, we are immediately done and can RAUW without further
delay.

3. If we do have an interior pointer instruction, we see if the immediate
guaranteed value we projected from has a single borrow introducer value. If not,
we bail. I think this is reasonable since with time, all guaranteed values will
always only have a single borrow introducing value (once struct, tuple,
destructure_struct, destructure_tuple become reborrows).

4. Then we gather up all inner uses of our access path. If for some reason that
fails, we bail.

5. Then we see if all of those uses are within our borrow scope. If so, we can
RAUW without any further worry.

6. Otherwise, we perform a copy+borrow of our interior pointer's operand value
at the interior pointer, create a copy of the interior pointer instruction upon
this new borrow and then RAUW oldValue with that instead. By construction all
uses of oldValue will be within this new interior pointer scope.
This commit is contained in:
Michael Gottesman
2021-01-01 12:44:46 -08:00
parent fe4c345d0d
commit 73ba521e56
7 changed files with 393 additions and 113 deletions

View File

@@ -669,6 +669,16 @@ struct InteriorPointerOperand {
/// requirements to ensure that the underlying class is alive at all use
/// points.
bool getImplicitUses(SmallVectorImpl<Operand *> &foundUses,
std::function<void(Operand *)> *onError = nullptr) {
return getImplicitUsesForAddress(getProjectedAddress(), foundUses, onError);
}
/// The algorithm that is used to determine what the verifier will consider to
/// be implicit uses of the given address. Used to implement \see
/// getImplicitUses.
static bool
getImplicitUsesForAddress(SILValue address,
SmallVectorImpl<Operand *> &foundUses,
std::function<void(Operand *)> *onError = nullptr);
Operand *operator->() { return operand; }

View File

@@ -38,18 +38,44 @@ struct OwnershipFixupContext {
DeadEndBlocks &deBlocks;
JointPostDominanceSetComputer &jointPostDomSetComputer;
OwnershipFixupContext(InstModCallbacks &callbacks, DeadEndBlocks &deBlocks,
JointPostDominanceSetComputer &inputJPDComputer)
: inlineCallbacks(), callbacks(callbacks), deBlocks(deBlocks),
jointPostDomSetComputer(inputJPDComputer) {}
/// Extra state initialized by OwnershipRAUWFixupHelper::get() that we use
/// when RAUWing addresses. This ensures we do not need to recompute this
/// state when we perform the actual RAUW.
struct AddressFixupContext {
/// When determining if we need to perform an address pointer fixup, we
/// compute all transitive address uses of oldValue. If we find that we do
/// need this fixed up, then we will copy our interior pointer base value
/// and use this to seed that new lifetime.
SmallVector<Operand *, 8> allAddressUsesFromOldValue;
OwnershipFixupContext(DeadEndBlocks &deBlocks,
JointPostDominanceSetComputer &inputJPDComputer)
: inlineCallbacks(InstModCallbacks()), callbacks(*inlineCallbacks),
deBlocks(deBlocks), jointPostDomSetComputer(inputJPDComputer) {}
/// This is the interior pointer operand that the new value we want to RAUW
/// is transitively derived from and enables us to know the underlying
/// borrowed base value that we need to lifetime extend.
InteriorPointerOperand intPtrOp;
void clear() {
allAddressUsesFromOldValue.clear();
intPtrOp = InteriorPointerOperand();
}
};
AddressFixupContext extraAddressFixupInfo;
OwnershipFixupContext(InstModCallbacks &callbacks, DeadEndBlocks &deBlocks,
JointPostDominanceSetComputer &jointPostDomSetComputer)
: callbacks(callbacks), deBlocks(deBlocks),
jointPostDomSetComputer(jointPostDomSetComputer) {}
void clear() {
jointPostDomSetComputer.clear();
extraAddressFixupInfo.allAddressUsesFromOldValue.clear();
extraAddressFixupInfo.intPtrOp = InteriorPointerOperand();
}
private:
/// Helper method called to determine if we discovered we needed interior
/// pointer fixups while simplifying.
bool needsInteriorPointerFixups() const {
return bool(extraAddressFixupInfo.intPtrOp);
}
};
@@ -57,20 +83,44 @@ struct OwnershipFixupContext {
/// value or a single value instruction with a new value and then fixup
/// ownership invariants afterwards.
class OwnershipRAUWHelper {
OwnershipFixupContext &ctx;
OwnershipFixupContext *ctx;
SingleValueInstruction *oldValue;
SILValue newValue;
public:
OwnershipRAUWHelper(OwnershipFixupContext &ctx) : ctx(ctx) {}
OwnershipRAUWHelper() : ctx(nullptr), oldValue(nullptr), newValue(nullptr) {}
SILBasicBlock::iterator
replaceAllUsesAndErase(SingleValueInstruction *oldValue, SILValue newValue);
/// We can not RAUW all old values with new values.
/// Return an instance of this class if we can perform the specific RAUW
/// operation ignoring if the types line up. Returns None otherwise.
///
/// Namely, we do not support RAUWing values with ValueOwnershipKind::None
/// that have uses that do not require ValueOwnershipKind::None or
/// ValueOwnershipKind::Any.
static bool canFixUpOwnershipForRAUW(SILValue oldValue, SILValue newValue);
/// DISCUSSION: We do not check that the types line up here so that we can
/// allow for our users to transform our new value in ways that preserve
/// ownership at \p oldValue before we perform the actual RAUW. If \p newValue
/// is an object, any instructions in the chain of transforming instructions
/// from \p newValue at \p oldValue's must be forwarding. If \p newValue is an
/// address, then these transforms can only transform the address into a
/// derived address.
OwnershipRAUWHelper(OwnershipFixupContext &ctx,
SingleValueInstruction *oldValue, SILValue newValue);
/// Returns true if this helper was initialized into a valid state.
operator bool() const { return isValid(); }
bool isValid() const { return bool(ctx) && bool(oldValue) && bool(newValue); }
/// Perform the actual RAUW. We require that \p newValue and \p oldValue have
/// the same type at this point (in contrast to when calling
/// OwnershipRAUWFixupHelper::get()).
///
/// This is so that we can avoid creating "forwarding" transformation
/// instructions before we know if we can perform the RAUW. Any such
/// "forwarding" transformation must be performed upon \p newValue at \p
/// oldValue's insertion point so that we can then here RAUW the transformed
/// \p newValue.
SILBasicBlock::iterator perform();
private:
SILBasicBlock::iterator replaceAddressUses(SingleValueInstruction *oldValue,
SILValue newValue);
};
} // namespace swift

View File

@@ -520,10 +520,9 @@ bool BorrowedValue::visitInteriorPointerOperands(
// InteriorPointerOperand
//===----------------------------------------------------------------------===//
bool InteriorPointerOperand::getImplicitUses(
SmallVectorImpl<Operand *> &foundUses,
bool InteriorPointerOperand::getImplicitUsesForAddress(
SILValue projectedAddress, SmallVectorImpl<Operand *> &foundUses,
std::function<void(Operand *)> *onError) {
SILValue projectedAddress = getProjectedAddress();
SmallVector<Operand *, 8> worklist(projectedAddress->getUses());
bool foundError = false;

View File

@@ -755,8 +755,8 @@ swift::replaceAllSimplifiedUsesAndErase(SILInstruction *i, SILValue result,
if (svi->getFunction()->hasOwnership()) {
JointPostDominanceSetComputer computer(*deadEndBlocks);
OwnershipFixupContext ctx{callbacks, *deadEndBlocks, computer};
OwnershipRAUWHelper helper(ctx);
return helper.replaceAllUsesAndErase(svi, result);
OwnershipRAUWHelper helper(ctx, svi, result);
return helper.perform();
}
return replaceAllUsesAndErase(svi, result, callbacks);
}
@@ -781,20 +781,7 @@ SILValue swift::simplifyOverflowBuiltinInstruction(BuiltinInst *BI) {
/// NOTE: We assume that the insertion point associated with the SILValue must
/// dominate \p i.
static SILValue simplifyInstruction(SILInstruction *i) {
SILValue result = InstSimplifier().visit(i);
if (!result)
return SILValue();
// If we have a result, we know that we must have a single value instruction
// by assumption since we have not implemented support in the rest of inst
// simplify for non-single value instructions. We put the cast here so that
// this code is not updated at this point in time.
auto *svi = cast<SingleValueInstruction>(i);
if (svi->getFunction()->hasOwnership())
if (!OwnershipRAUWHelper::canFixUpOwnershipForRAUW(svi, result))
return SILValue();
return result;
return InstSimplifier().visit(i);
}
SILBasicBlock::iterator swift::simplifyAndReplaceAllSimplifiedUsesAndErase(
@@ -811,11 +798,16 @@ SILBasicBlock::iterator swift::simplifyAndReplaceAllSimplifiedUsesAndErase(
if (!result || svi == result)
return next;
if (svi->getFunction()->hasOwnership()) {
if (!svi->getFunction()->hasOwnership())
return replaceAllUsesAndErase(svi, result, callbacks);
JointPostDominanceSetComputer computer(*deadEndBlocks);
OwnershipFixupContext ctx{callbacks, *deadEndBlocks, computer};
OwnershipRAUWHelper helper(ctx);
return helper.replaceAllUsesAndErase(svi, result);
}
return replaceAllUsesAndErase(svi, result, callbacks);
OwnershipRAUWHelper helper(ctx, svi, result);
// If our RAUW helper is invalid, we do not support RAUWing this case, so
// just return next.
if (!helper.isValid())
return next;
return helper.perform();
}

View File

@@ -96,11 +96,8 @@ class SILCombiner :
/// post-dominating blocks to a full jointly post-dominating set.
JointPostDominanceSetComputer jPostDomComputer;
/// A utility that we use to perform erase+RAUW that fixes up ownership for us
/// afterwards by lifetime extending/copy values as appropriate. We rely on
/// later optimizations to chew through this traffic. This ensures we can use
/// one code base for both OSSA and non-OSSA.
OwnershipFixupContext ownershipRAUWHelper;
/// External context struct used by \see ownershipRAUWHelper.
OwnershipFixupContext ownershipFixupContext;
public:
SILCombiner(SILOptFunctionBuilder &FuncBuilder, SILBuilder &B,
@@ -134,7 +131,7 @@ public:
Worklist.add(use->getUser());
}),
deBlocks(&B.getFunction()), jPostDomComputer(deBlocks),
ownershipRAUWHelper(instModCallbacks, deBlocks, jPostDomComputer) {}
ownershipFixupContext(instModCallbacks, deBlocks, jPostDomComputer) {}
bool runOnFunction(SILFunction &F);

View File

@@ -583,7 +583,7 @@ public:
DeadEndBlocks &DeadEndBBs;
OwnershipRAUWHelper &RAUWHelper;
OwnershipFixupContext &RAUWFixupContext;
/// The set of calls to lazy property getters which can be replace by a direct
/// load of the property value.
@@ -591,9 +591,10 @@ public:
CSE(bool RunsOnHighLevelSil, SideEffectAnalysis *SEA,
SILOptFunctionBuilder &FuncBuilder, DeadEndBlocks &DeadEndBBs,
OwnershipRAUWHelper &RAUWHelper)
OwnershipFixupContext &RAUWFixupContext)
: SEA(SEA), FuncBuilder(FuncBuilder), DeadEndBBs(DeadEndBBs),
RAUWHelper(RAUWHelper), RunsOnHighLevelSil(RunsOnHighLevelSil) {}
RAUWFixupContext(RAUWFixupContext),
RunsOnHighLevelSil(RunsOnHighLevelSil) {}
bool processFunction(SILFunction &F, DominanceInfo *DT);
@@ -1017,14 +1018,14 @@ bool CSE::processNode(DominanceInfoNode *Node) {
// extend it here as well
if (!isa<SingleValueInstruction>(Inst))
continue;
if (!OwnershipRAUWHelper::canFixUpOwnershipForRAUW(
cast<SingleValueInstruction>(Inst),
cast<SingleValueInstruction>(AvailInst)))
continue;
// Replace SingleValueInstruction using OSSA RAUW here
nextI = RAUWHelper.replaceAllUsesAndErase(
OwnershipRAUWHelper helper(RAUWFixupContext,
cast<SingleValueInstruction>(Inst),
cast<SingleValueInstruction>(AvailInst));
if (!helper.isValid())
continue;
// Replace SingleValueInstruction using OSSA RAUW here
nextI = helper.perform();
Changed = true;
++NumCSE;
continue;
@@ -1399,8 +1400,7 @@ class SILCSE : public SILFunctionTransform {
JointPostDominanceSetComputer Computer(DeadEndBBs);
InstModCallbacks callbacks;
OwnershipFixupContext FixupCtx{callbacks, DeadEndBBs, Computer};
OwnershipRAUWHelper RAUWHelper(FixupCtx);
CSE C(RunsOnHighLevelSil, SEA, FuncBuilder, DeadEndBBs, RAUWHelper);
CSE C(RunsOnHighLevelSil, SEA, FuncBuilder, DeadEndBBs, FixupCtx);
bool Changed = false;
// Perform the traditional CSE.

View File

@@ -22,6 +22,7 @@
#include "swift/SIL/BasicBlockUtils.h"
#include "swift/SIL/InstructionUtils.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"
@@ -159,6 +160,58 @@ static void getAllNonTrivialUsePointsOfBorrowedValue(
}
}
// All callers of our RAUW routines must ensure that their values return true
// from this.
static bool 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;
}
//===----------------------------------------------------------------------===//
// Ownership Lifetime Extender
//===----------------------------------------------------------------------===//
@@ -477,7 +530,7 @@ static void rewriteReborrows(SILValue newBorrowedValue,
}
//===----------------------------------------------------------------------===//
// Ownership Fixup RAUW
// OwnershipRAUWUtility - RAUW + fix ownership
//===----------------------------------------------------------------------===//
/// Given an old value and a new value, lifetime extend new value as appropriate
@@ -644,7 +697,7 @@ SILBasicBlock::iterator OwnershipRAUWUtility::handleGuaranteed() {
SILBasicBlock::iterator OwnershipRAUWUtility::perform() {
assert(oldValue->getFunction()->hasOwnership());
assert(
OwnershipRAUWHelper::canFixUpOwnershipForRAUW(oldValue, newValue) &&
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.
@@ -684,65 +737,244 @@ SILBasicBlock::iterator OwnershipRAUWUtility::perform() {
}
//===----------------------------------------------------------------------===//
// Ownership Fixup Context
// Interior Pointer Operand Rebasing
//===----------------------------------------------------------------------===//
// All callers of our RAUW routines must ensure that their values return true
// from this.
bool OwnershipRAUWHelper::canFixUpOwnershipForRAUW(SILValue oldValue,
SILValue newValue) {
auto newOwnershipKind = newValue.getOwnershipKind();
namespace {
// 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;
/// Clone all projections and casts on the access use-def chain until either the
/// specified predicate is true or the access base is reached.
///
/// This will not clone ref_element_addr or ref_tail_addr because those aren't
/// part of the access chain.
class InteriorPointerAddressRebaseUseDefChainCloner
: public AccessUseDefChainVisitor<
InteriorPointerAddressRebaseUseDefChainCloner, SILValue> {
SILValue oldAddressValue;
SingleValueInstruction *oldIntPtr;
SingleValueInstruction *newIntPtr;
SILInstruction *insertPt;
// 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;
public:
InteriorPointerAddressRebaseUseDefChainCloner(
SILValue oldAddressValue, SingleValueInstruction *oldIntPtr,
SingleValueInstruction *newIntPtr, SILInstruction *insertPt)
: oldAddressValue(oldAddressValue), oldIntPtr(oldIntPtr),
newIntPtr(newIntPtr), insertPt(insertPt) {}
// 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);
// Recursive main entry point
SILValue cloneUseDefChain(SILValue currentOldAddr) {
// If we have finally hit oldIntPtr, we are done.
if (currentOldAddr == oldIntPtr)
return currentOldAddr;
return this->visit(currentOldAddr);
}
// If we are in Raw SIL, just bail at this point. We do not support
// ownership fixups.
if (m->getStage() == SILStage::Raw)
return false;
// Recursively clone an address on the use-def chain.
SingleValueInstruction *cloneProjection(SingleValueInstruction *projectedAddr,
Operand *sourceOper) {
SILValue sourceOperVal = sourceOper->get();
SILValue projectedSource = cloneUseDefChain(sourceOperVal);
// If we hit the end of our chain, then make newIntPtr the operand so that
// we have successfully rebased.
if (sourceOperVal == projectedSource)
projectedSource = newIntPtr;
SILInstruction *clone = projectedAddr->clone(insertPt);
clone->setOperand(sourceOper->getOperandNumber(), projectedSource);
return cast<SingleValueInstruction>(clone);
}
// 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;
SILValue visitBase(SILValue base, AccessedStorage::Kind kind) {
assert(false && "access base cannot be cloned");
return SILValue();
}
// 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;
SILValue visitNonAccess(SILValue base) {
assert(false && "unknown address root cannot be cloned");
return SILValue();
}
SILValue visitPhi(SILPhiArgument *phi) {
assert(false && "unexpected phi on access path");
return SILValue();
}
SILValue visitStorageCast(SingleValueInstruction *cast, Operand *sourceOper) {
assert(false && "unexpected storage cast on access path");
return SILValue();
}
SILValue visitAccessProjection(SingleValueInstruction *projectedAddr,
Operand *sourceOper) {
return cloneProjection(projectedAddr, sourceOper);
}
};
} // namespace
/// \p oldAddressValue is an address rooted in \p oldIntPtr. Clone the use-def
/// chain from \p oldAddressValue to \p oldIntPtr, but starting from \p
/// newAddressValue.
static SILValue cloneInteriorProjectionUseDefChain(
SILValue oldAddressValue, SingleValueInstruction *oldIntPtr,
SingleValueInstruction *newIntPtr, SILInstruction *insertPt) {
InteriorPointerAddressRebaseUseDefChainCloner cloner(
oldAddressValue, oldIntPtr, newIntPtr, insertPt);
return cloner.cloneUseDefChain(oldAddressValue);
}
SILBasicBlock::iterator
OwnershipRAUWHelper::replaceAllUsesAndErase(SingleValueInstruction *oldValue,
OwnershipRAUWHelper::replaceAddressUses(SingleValueInstruction *oldValue,
SILValue newValue) {
OwnershipRAUWUtility utility{oldValue, newValue, ctx};
assert(oldValue->getType().isAddress() &&
oldValue->getType() == newValue->getType());
// If we are replacing addresses, see if we need to handle interior pointer
// fixups. If we don't have any extra info, then we know that we can just RAUW
// without any further work.
if (!ctx->extraAddressFixupInfo.intPtrOp)
return replaceAllUsesAndErase(oldValue, newValue, ctx->callbacks);
// We are RAUWing two addresses and we found that:
//
// 1. newValue is an address associated with an interior pointer instruction.
// 2. oldValue has uses that are outside of newValue's borrow scope.
//
// So, we need to copy/borrow the base value of the interior pointer to
// lifetime extend the base value over the new uses. Then we clone the
// interior pointer instruction and change the clone to use our new borrowed
// value. Then we RAUW as appropriate.
OwnershipLifetimeExtender extender{*ctx};
auto &extraInfo = ctx->extraAddressFixupInfo;
auto intPtr = *extraInfo.intPtrOp;
BeginBorrowInst *bbi = extender.createPlusZeroBorrow(
intPtr->get(), llvm::makeArrayRef(extraInfo.allAddressUsesFromOldValue));
auto bbiNext = &*std::next(bbi->getIterator());
auto *newIntPtrUser =
cast<SingleValueInstruction>(intPtr->getUser()->clone(bbiNext));
ctx->callbacks.createdNewInst(newIntPtrUser);
newIntPtrUser->setOperand(0, bbi);
// Now that we have extended our lifetime as appropriate, we need to recreate
// the access path from newValue to intPtr but upon newIntPtr. Then we make it
// use newIntPtr.
auto *intPtrUser = cast<SingleValueInstruction>(intPtr->getUser());
SILValue initialAddr = cloneInteriorProjectionUseDefChain(
newValue /*address we originally wanted to replace*/,
intPtrUser /*the interior pointer of that value*/,
newIntPtrUser /*the interior pointer we need to recreate the chain upon*/,
oldValue /*insert point*/);
// If we got back newValue, then we need to set initialAddr to be int ptr
// user.
if (initialAddr == newValue)
initialAddr = newIntPtrUser;
// Now that we have an addr that is setup appropriately, RAUW!
return replaceAllUsesAndErase(oldValue, initialAddr, ctx->callbacks);
}
//===----------------------------------------------------------------------===//
// Top Level Entrypoints
//===----------------------------------------------------------------------===//
OwnershipRAUWHelper::OwnershipRAUWHelper(OwnershipFixupContext &inputCtx,
SingleValueInstruction *inputOldValue,
SILValue inputNewValue)
: ctx(&inputCtx), oldValue(inputOldValue), newValue(inputNewValue) {
// If we are already not valid, just bail.
if (!isValid())
return;
// Otherwise, lets check if we can perform this RAUW operation. If we can't,
// set ctx to nullptr to invalidate the helper and return.
if (!canFixUpOwnershipForRAUW(oldValue, newValue)) {
ctx = nullptr;
return;
}
// If we have an object, at this point we are good to go so we can just
// return.
if (newValue->getType().isObject())
return;
// But if we have an address, we need to check if new value is from an
// interior pointer or not in a way that the pass understands. What we do is:
//
// 1. Compute the AccessPathWithBase of newValue. If we do not get back a
// valid such object, invalidate and then bail.
//
// 2. Then we check if the base address is the result of an interior pointer
// instruction. If we do not find one we bail.
//
// 3. Then grab the base value of the interior pointer operand. We only
// support cases where we have a single BorrowedValue as our base. This is
// a safe future proof assumption since one reborrows are on
// structs/tuple/destructures, a guaranteed value will always be associated
// with a single BorrowedValue, so this will never fail (and the code will
// probably be DCEed).
//
// 4. Then we compute an AccessPathWithBase for oldValue and then find its
// derived uses. If we fail, we bail.
//
// 5. At this point, we know that we can perform this RAUW. The only question
// is if we need to when we RAUW copy the interior pointer base value. We
// perform this check by making sure all of the old value's derived uses
// are within our BorrowedValue's scope. If so, we clear the extra state we
// were tracking (the interior pointer/oldValue's transitive uses), so we
// perform just a normal RAUW (without inserting the copy) when we RAUW.
auto accessPathWithBase = AccessPathWithBase::compute(newValue);
if (!accessPathWithBase.base) {
// Invalidate!
ctx = nullptr;
return;
}
auto &intPtr = ctx->extraAddressFixupInfo.intPtrOp;
intPtr = InteriorPointerOperand::inferFromResult(accessPathWithBase.base);
if (!intPtr) {
// We can optimize! Do not invalidate!
return;
}
auto borrowedValue = intPtr.getSingleBaseValue();
if (!borrowedValue) {
// Invalidate!
ctx = nullptr;
return;
}
// For now, just gather up uses
auto &oldValueUses = ctx->extraAddressFixupInfo.allAddressUsesFromOldValue;
if (InteriorPointerOperand::getImplicitUsesForAddress(oldValue,
oldValueUses)) {
// If we found an error, invalidate and return!
ctx = nullptr;
return;
}
// Ok, at this point we know that we can optimize. The only question is if we
// need to perform the copy or not when we actually RAUW. So perform the is
// within region check. If we succeed, clear our extra state so we perform a
// normal RAUW.
SmallVector<Operand *, 8> scratchSpace;
SmallPtrSet<SILBasicBlock *, 8> visitedBlocks;
if (borrowedValue.areUsesWithinScope(oldValueUses, scratchSpace,
visitedBlocks, ctx->deBlocks)) {
// We do not need to copy the base value! Clear the extra info we have.
ctx->extraAddressFixupInfo.clear();
}
}
SILBasicBlock::iterator OwnershipRAUWHelper::perform() {
assert(isValid() && "OwnershipRAUWHelper invalid?!");
// Make sure to always clear our context after we transform.
SWIFT_DEFER { ctx->clear(); };
if (oldValue->getType().isAddress())
return replaceAddressUses(oldValue, newValue);
OwnershipRAUWUtility utility{oldValue, newValue, *ctx};
return utility.perform();
}