mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Also fixed a small bug related to how we handle arguments. We were not clearing the state of the argument when we saw a potential decrement of the argument. <rdar://problem/17013194> Swift SVN r18706
517 lines
18 KiB
C++
517 lines
18 KiB
C++
//===- ReferenceCountState.h - Represents a Reference Count -----*- C++ -*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SWIFT_SILANALYSIS_REFERENCECOUNTSTATE_H
|
|
#define SWIFT_SILANALYSIS_REFERENCECOUNTSTATE_H
|
|
|
|
#include "swift/Basic/Fallthrough.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/SILArgument.h"
|
|
#include "swift/SIL/SILBasicBlock.h"
|
|
#include "swift/SILAnalysis/ARCAnalysis.h"
|
|
#include <algorithm>
|
|
|
|
namespace swift {
|
|
class AliasAnalysis;
|
|
} // end namespace swift
|
|
|
|
namespace swift {
|
|
namespace arc {
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Utility
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Is I an instruction that we recognize as a "reference count increment"
|
|
/// instruction?
|
|
static inline bool isRefCountIncrement(SILInstruction &I) {
|
|
return isa<StrongRetainInst>(I) || isa<RetainValueInst>(I);
|
|
}
|
|
|
|
/// Is I an instruction that we recognize as a "reference count decrement"
|
|
/// instruction?
|
|
static inline bool isRefCountDecrement(SILInstruction &I) {
|
|
return isa<StrongReleaseInst>(I) || isa<ReleaseValueInst>(I);
|
|
}
|
|
|
|
/// Returns true if Inc and Dec are compatible reference count instructions.
|
|
///
|
|
/// In more specific terms this means that means a (strong_retain,
|
|
/// strong_release) pair or a (retain_value, release_value) pair.
|
|
static inline bool matchingRefCountPairType(SILInstruction *I1,
|
|
SILInstruction *I2) {
|
|
// Always put the strong retain on the left.
|
|
if (isa<StrongRetainInst>(I2) || isa<RetainValueInst>(I2))
|
|
std::swap(I1, I2);
|
|
|
|
// Now we know that I1 should be an increment and I2 a decrement. Assert to
|
|
// check for programmer error.
|
|
assert(isRefCountIncrement(*I1) && isRefCountDecrement(*I2) &&
|
|
"Submitting two increments or decrements to this function is "
|
|
"invalid.");
|
|
return (isa<StrongRetainInst>(I1) && isa<StrongReleaseInst>(I2)) ||
|
|
(isa<RetainValueInst>(I1) && isa<ReleaseValueInst>(I2));
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Ref Count State
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// A struct that abstracts over reference counts manipulated by strong_retain,
|
|
/// retain_value, strong_release,
|
|
template <typename ImplStruct>
|
|
struct RefCountState {
|
|
using InstructionSet = llvm::SmallPtrSet<SILInstruction *, 8>;
|
|
|
|
/// Return the SILValue that we are tracking.
|
|
SILValue Value;
|
|
|
|
/// Was the pointer we are tracking known incremented when we visited the
|
|
/// current increment we are tracking? In that case we know that it is safe
|
|
/// to move the inner retain over instructions that may decrement ref counts
|
|
/// since the outer retain will keep the reference counted value alive.
|
|
bool KnownSafe = false;
|
|
|
|
/// The latest point we can move Instruction without moving it over an
|
|
/// instruction that might be able to decrement the value with reference
|
|
/// semantics.
|
|
InstructionSet InsertPts;
|
|
|
|
/// Have we performed any partial merges of insertion points? We can not
|
|
/// perform two partial merges in a row unless we are able to reason about
|
|
/// control dependency (which avoid for now).
|
|
bool Partial = false;
|
|
|
|
/// Return this as the CRTP substruct.
|
|
ImplStruct *asImpl() { return static_cast<ImplStruct *>(this); }
|
|
ImplStruct *asImpl() const {
|
|
return const_cast<RefCountState<ImplStruct> *>(this)->asImpl();
|
|
}
|
|
|
|
RefCountState() {}
|
|
~RefCountState() {}
|
|
|
|
/// Initializes/reinitialized the state for I. If we reinitialize we return
|
|
/// true.
|
|
bool initWithInst(SILInstruction *I) {
|
|
// Are we already tracking a ref count modification?
|
|
bool Nested = isTrackingRefCount();
|
|
|
|
// Initialize value.
|
|
Value = I->getOperand(0).stripCasts();
|
|
|
|
// This retain is known safe if the operand we are tracking was already
|
|
// known incremented previously. This occurs when you have nested
|
|
// increments.
|
|
KnownSafe = isRefCountStateModified();
|
|
|
|
// Clear our insertion point list.
|
|
InsertPts.clear();
|
|
|
|
return Nested;
|
|
}
|
|
|
|
/// Uninitialize the current state.
|
|
void clear() {
|
|
asImpl()->clear();
|
|
Value = SILValue();
|
|
KnownSafe = false;
|
|
InsertPts.clear();
|
|
}
|
|
|
|
/// Can we gaurantee that the given reference counted value has been modified?
|
|
bool isRefCountStateModified() const {
|
|
return asImpl()->isRefCountStateModified();
|
|
}
|
|
|
|
/// Is this ref count initialized and tracking a ref count ptr.
|
|
bool isTrackingRefCount() const {
|
|
return asImpl()->isTrackingRefCount();
|
|
}
|
|
|
|
/// Are we tracking an instruction currently? This returns false when given an
|
|
/// uninitialized ReferenceCountState.
|
|
bool isTrackingRefCountInst() const {
|
|
return asImpl()->isTrackingRefCountInst();
|
|
}
|
|
|
|
/// Are we tracking a source of ref counts? This currently means that we are
|
|
/// tracking an argument that is @owned. In the future this will include
|
|
/// return values of functions that are @owned.
|
|
bool isTrackingRefCountSource() const {
|
|
return asImpl()->isTrackingRefCountSource();
|
|
}
|
|
|
|
/// Return the increment we are tracking.
|
|
Range<InstructionSet::iterator> getInstructions() const {
|
|
return asImpl()->getInstructions();
|
|
}
|
|
|
|
/// Returns true if I is in the instructions we are tracking.
|
|
bool containsInstruction(SILInstruction *I) const {
|
|
return asImpl()->containsInstruction(I);
|
|
}
|
|
|
|
/// Return the value with reference semantics that is the operand of our
|
|
/// increment.
|
|
SILValue getValue() const {
|
|
return Value;
|
|
}
|
|
|
|
/// The latest point we can move the increment without bypassing instructions
|
|
/// that may have reference semantics.
|
|
Range<InstructionSet::iterator> getInsertPts() const {
|
|
return {InsertPts.begin(), InsertPts.end()};
|
|
}
|
|
|
|
/// This retain is known safe if the operand we are tracking was already
|
|
/// known incremented previously. This occurs when you have nested
|
|
/// increments.
|
|
bool isKnownSafe() const { return KnownSafe; }
|
|
|
|
/// Check if PotentialDecrement can decrement the reference count associated
|
|
/// with the value we are tracking. If so advance the state's sequence
|
|
/// appropriately and return true. Otherwise return false.
|
|
bool handlePotentialDecrement(SILInstruction *PotentialDecrement,
|
|
AliasAnalysis *AA) {
|
|
// If we are not tracking a ref count, just return false.
|
|
if (!isTrackingRefCount())
|
|
return false;
|
|
|
|
// If we can prove that Other can not use the pointer we are tracking,
|
|
// return...
|
|
if (!canDecrementRefCount(PotentialDecrement, getValue(), AA))
|
|
return false;
|
|
|
|
// Otherwise, allow the CRTP substruct to update itself given we have a
|
|
// potential decrement.
|
|
return asImpl()->handleDecrement(PotentialDecrement);
|
|
}
|
|
|
|
/// Check if PotentialUser could be a use of the reference counted value that
|
|
/// requires user to be alive. If so advance the state's sequence
|
|
/// appropriately and return true. Otherwise return false.
|
|
bool handlePotentialUser(SILInstruction *PotentialUser, AliasAnalysis *AA) {
|
|
// If we are not tracking a ref count, just return false.
|
|
if (!isTrackingRefCount())
|
|
return false;
|
|
|
|
if (!canUseValue(PotentialUser, getValue(), AA))
|
|
return false;
|
|
|
|
return asImpl()->handleUser(PotentialUser);
|
|
}
|
|
|
|
/// Returns true if the passed in ref count inst matches the ref count inst
|
|
/// we are tracking. This handles generically retains/release.
|
|
bool isRefCountInstMatchedToTrackedInstruction(SILInstruction *RefCountInst) {
|
|
// If our state is not initialized, return false since we are not tracking
|
|
// anything. If the ValueKind of our increments, decrement do not match, we
|
|
// can not eliminate the pair so return false.
|
|
if (!isTrackingRefCountInst() ||
|
|
!matchingRefCountPairType(*getInstructions().begin(), RefCountInst))
|
|
return false;
|
|
return asImpl()->handleRefCountInstMatch(RefCountInst);
|
|
}
|
|
};
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Bottom Up Ref Count State
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
struct BottomUpRefCountState : public RefCountState<BottomUpRefCountState> {
|
|
using Super = RefCountState<BottomUpRefCountState>;
|
|
|
|
/// Sequence of states that a value with reference semantics can go through
|
|
/// when visiting decrements bottom up. The reason why I have this separate
|
|
/// from TopDownSubstruct is I think it gives more clarity to the algorithm
|
|
/// by giving it typed form.
|
|
enum class LatticeState {
|
|
None, ///< The pointer has no information associated with it.
|
|
Decremented, ///< The pointer will be decremented.
|
|
MightBeUsed, ///< The pointer will be used and then at this point
|
|
/// be decremented
|
|
MightBeDecremented, ///< The pointer might be decremented again implying
|
|
/// that we can not, without being known safe remove
|
|
/// this decrement.
|
|
};
|
|
|
|
/// The set of decrements that we are tracking.
|
|
llvm::SmallPtrSet<SILInstruction *, 8> Decrements;
|
|
|
|
/// Current place in the sequence of the value.
|
|
LatticeState LatState = LatticeState::None;
|
|
|
|
/// Initializes/reinitialized the state for I. If we reinitialize we return
|
|
/// true.
|
|
bool initWithInst(SILInstruction *I) {
|
|
assert((isa<StrongReleaseInst>(I) || isa<ReleaseValueInst>(I)) &&
|
|
"strong_release and release_value are only supported.");
|
|
|
|
bool NestingDetected = Super::initWithInst(I);
|
|
|
|
// Clear our decrement list and insert I into it.
|
|
Decrements.clear();
|
|
Decrements.insert(I);
|
|
|
|
// Set our lattice state to be incremented.
|
|
LatState = LatticeState::Decremented;
|
|
|
|
return NestingDetected;
|
|
}
|
|
|
|
/// Uninitialize the current state.
|
|
void clear() {
|
|
Decrements.clear();
|
|
LatState = LatticeState::None;
|
|
}
|
|
|
|
/// Can we gaurantee that the given reference counted value has been modified?
|
|
bool isRefCountStateModified() const {
|
|
return LatState == LatticeState::Decremented;
|
|
}
|
|
|
|
/// Is this ref count initialized and tracking a ref count ptr.
|
|
bool isTrackingRefCount() const {
|
|
return Decrements.size();
|
|
}
|
|
|
|
/// Are we tracking an instruction currently? This returns false when given an
|
|
/// uninitialized ReferenceCountState.
|
|
bool isTrackingRefCountInst() const {
|
|
return Decrements.size();
|
|
}
|
|
|
|
/// Are we tracking a source of ref counts? This currently means that we are
|
|
/// tracking an argument that is @owned. In the future this will include
|
|
/// return values of functions that are @owned.
|
|
bool isTrackingRefCountSource() const {
|
|
return false;
|
|
}
|
|
|
|
/// Returns true if I is an instruction that we are tracking.
|
|
bool containsInstruction(SILInstruction *I) const {
|
|
return Decrements.count(I);
|
|
}
|
|
|
|
/// Return the increment we are tracking.
|
|
Range<decltype(Decrements)::iterator> getInstructions() const {
|
|
return {Decrements.begin(), Decrements.end()};
|
|
}
|
|
|
|
/// If advance the state's sequence appropriately for a decrement. If we do
|
|
/// advance return true. Otherwise return false.
|
|
bool handleDecrement(SILInstruction *PotentialDecrement) {
|
|
switch (LatState) {
|
|
case LatticeState::MightBeUsed:
|
|
LatState = LatticeState::MightBeDecremented;
|
|
return true;
|
|
case LatticeState::None:
|
|
case LatticeState::MightBeDecremented:
|
|
case LatticeState::Decremented:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Given the current lattice state, if we have seen a use, advance the
|
|
/// lattice state. Return true if we do so and false otherwise.
|
|
bool handleUser(SILInstruction *PotentialUser) {
|
|
// Advance the sequence...
|
|
switch (LatState) {
|
|
case LatticeState::Decremented:
|
|
LatState = LatticeState::MightBeUsed;
|
|
assert(InsertPts.empty() && "If we are decremented, we should have no "
|
|
"insertion points.");
|
|
InsertPts.insert(std::next(SILBasicBlock::iterator(PotentialUser)));
|
|
return true;
|
|
case LatticeState::MightBeUsed:
|
|
case LatticeState::MightBeDecremented:
|
|
case LatticeState::None:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// We have a matching ref count inst. Return true if we advance the sequence
|
|
/// and false otherwise.
|
|
bool handleRefCountInstMatch(SILInstruction *RefCountInst) {
|
|
// Otherwise modify the state appropriately in preparation for removing the
|
|
// increment, decrement pair.
|
|
switch (LatState) {
|
|
case LatticeState::None:
|
|
return false;
|
|
case LatticeState::Decremented:
|
|
case LatticeState::MightBeUsed:
|
|
// Unset InsertPt so we remove retain release pairs instead of
|
|
// performing code motion.
|
|
InsertPts.clear();
|
|
SWIFT_FALLTHROUGH;
|
|
case LatticeState::MightBeDecremented:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool merge(const BottomUpRefCountState &Other);
|
|
};
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Top Down Ref Count State
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
struct TopDownRefCountState : public RefCountState<TopDownRefCountState> {
|
|
using Super = RefCountState<TopDownRefCountState>;
|
|
|
|
/// Sequence of states that a value with reference semantics can go through
|
|
/// when visiting decrements bottom up. The reason why I have this separate
|
|
/// from BottomUpRefCountState is I think it gives more clarity to the
|
|
/// algorithm by giving it typed form.
|
|
enum class LatticeState {
|
|
None, ///< The pointer has no information associated with it.
|
|
Incremented, ///< The pointer has been incremented.
|
|
MightBeDecremented, ///< The pointer has been incremented and might be
|
|
/// decremented. be decremented again implying
|
|
MightBeUsed, ///< The pointer has been incremented,
|
|
};
|
|
|
|
/// The increment/increment set that we are tracking.
|
|
NullablePtr<SILArgument> Argument;
|
|
llvm::SmallPtrSet<SILInstruction *, 8> Increments;
|
|
|
|
/// Current place in the sequence of the value.
|
|
LatticeState LatState = LatticeState::None;
|
|
|
|
/// Initializes/reinitialized the state for I. If we reinitialize we return
|
|
/// true.
|
|
bool initWithInst(SILInstruction *I) {
|
|
assert((isa<StrongRetainInst>(I) || isa<RetainValueInst>(I)) &&
|
|
"strong_retain and retain_value are only supported.");
|
|
|
|
bool NestingDetected = Super::initWithInst(I);
|
|
|
|
// Clear our tracked set of increments and add I to the list.
|
|
Argument = nullptr;
|
|
Increments.clear();
|
|
Increments.insert(I);
|
|
|
|
// Set our lattice state to be incremented.
|
|
LatState = LatticeState::Incremented;
|
|
|
|
return NestingDetected;
|
|
}
|
|
|
|
void initWithArg(SILArgument *Arg) {
|
|
LatState = LatticeState::Incremented;
|
|
Increments.clear();
|
|
Argument = Arg;
|
|
Value = Arg;
|
|
KnownSafe = false;
|
|
InsertPts.clear();
|
|
}
|
|
|
|
/// Uninitialize the current state.
|
|
void clear() {
|
|
Argument = nullptr;
|
|
Increments.clear();
|
|
LatState = LatticeState::None;
|
|
}
|
|
|
|
/// Can we gaurantee that the given reference counted value has been modified?
|
|
bool isRefCountStateModified() const {
|
|
return LatState == LatticeState::Incremented;
|
|
}
|
|
|
|
/// Is this ref count initialized and tracking a ref count ptr.
|
|
bool isTrackingRefCount() const {
|
|
return !Increments.empty() || !Argument.isNull();
|
|
}
|
|
|
|
/// Are we tracking an instruction currently? This returns false when given an
|
|
/// uninitialized ReferenceCountState.
|
|
bool isTrackingRefCountInst() const {
|
|
return Increments.size();
|
|
}
|
|
|
|
/// Are we tracking a source of ref counts? This currently means that we are
|
|
/// tracking an argument that is @owned. In the future this will include
|
|
/// return values of functions that are @owned.
|
|
bool isTrackingRefCountSource() const {
|
|
return !Argument.isNull();
|
|
}
|
|
|
|
/// Returns true if I is an instruction that we are tracking.
|
|
bool containsInstruction(SILInstruction *I) const {
|
|
return Increments.count(I);
|
|
}
|
|
|
|
/// Return the increment we are tracking.
|
|
Range<decltype(Increments)::iterator> getInstructions() const {
|
|
assert(Argument.isNull() &&
|
|
"Should never call this when processing an argument.");
|
|
return {Increments.begin(), Increments.end()};
|
|
}
|
|
|
|
/// If advance the state's sequence appropriately for a decrement. If we do
|
|
/// advance return true. Otherwise return false.
|
|
bool handleDecrement(SILInstruction *PotentialDecrement) {
|
|
switch (LatState) {
|
|
case LatticeState::Incremented:
|
|
LatState = LatticeState::MightBeDecremented;
|
|
InsertPts.insert(PotentialDecrement);
|
|
return true;
|
|
case LatticeState::None:
|
|
case LatticeState::MightBeDecremented:
|
|
case LatticeState::MightBeUsed:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Given the current lattice state, if we have seen a use, advance the
|
|
/// lattice state. Return true if we do so and false otherwise.
|
|
bool handleUser(SILInstruction *PotentialUser) {
|
|
// Otherwise advance the sequence...
|
|
switch (LatState) {
|
|
case LatticeState::MightBeDecremented:
|
|
LatState = LatticeState::MightBeUsed;
|
|
return true;
|
|
case LatticeState::Incremented:
|
|
case LatticeState::None:
|
|
case LatticeState::MightBeUsed:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// We have a matching ref count inst. Return true if we advance the sequence
|
|
/// and false otherwise.
|
|
bool handleRefCountInstMatch(SILInstruction *RefCountInst) {
|
|
// Otherwise modify the state appropriately in preparation for removing the
|
|
// increment, decrement pair.
|
|
switch (LatState) {
|
|
case LatticeState::None:
|
|
return false;
|
|
case LatticeState::Incremented:
|
|
case LatticeState::MightBeDecremented:
|
|
// Unset InsertPt so we remove retain release pairs instead of performing
|
|
// code motion.
|
|
InsertPts.clear();
|
|
SWIFT_FALLTHROUGH;
|
|
case LatticeState::MightBeUsed:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool merge(const TopDownRefCountState &Other);
|
|
};
|
|
|
|
} // end arc namespace
|
|
} // end swift namespace
|
|
|
|
#endif
|