blob: dc87f648b6080b05c3b2038c832f2e10db35bad9 [file] [log] [blame]
//===--- PMOMemoryUseCollector.cpp - Memory use analysis for PMO ----------===//
//
// 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 "definite-init"
#include "PMOMemoryUseCollector.h"
#include "swift/AST/Expr.h"
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBuilder.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/SaveAndRestore.h"
using namespace swift;
//===----------------------------------------------------------------------===//
// PMOMemoryObjectInfo Implementation
//===----------------------------------------------------------------------===//
PMOMemoryObjectInfo::PMOMemoryObjectInfo(AllocationInst *allocation)
: MemoryInst(allocation) {
auto &module = MemoryInst->getModule();
// Compute the type of the memory object.
if (auto *abi = dyn_cast<AllocBoxInst>(MemoryInst)) {
assert(abi->getBoxType()->getLayout()->getFields().size() == 1 &&
"analyzing multi-field boxes not implemented");
MemorySILType = abi->getBoxType()->getFieldType(module, 0);
} else {
MemorySILType = cast<AllocStackInst>(MemoryInst)->getElementType();
}
}
SILInstruction *PMOMemoryObjectInfo::getFunctionEntryPoint() const {
return &*getFunction().begin()->begin();
}
//===----------------------------------------------------------------------===//
// Scalarization Logic
//===----------------------------------------------------------------------===//
/// Given a pointer to a tuple type, compute the addresses of each element and
/// add them to the ElementAddrs vector.
static void
getScalarizedElementAddresses(SILValue Pointer, SILBuilder &B, SILLocation Loc,
SmallVectorImpl<SILValue> &ElementAddrs) {
TupleType *TT = Pointer->getType().castTo<TupleType>();
for (auto Index : indices(TT->getElements())) {
ElementAddrs.push_back(B.createTupleElementAddr(Loc, Pointer, Index));
}
}
/// Scalarize a load down to its subelements. If NewLoads is specified, this
/// can return the newly generated sub-element loads.
static SILValue scalarizeLoad(LoadInst *LI,
SmallVectorImpl<SILValue> &ElementAddrs) {
SILBuilderWithScope B(LI);
SmallVector<SILValue, 4> ElementTmps;
for (unsigned i = 0, e = ElementAddrs.size(); i != e; ++i) {
auto *SubLI = B.createTrivialLoadOr(LI->getLoc(), ElementAddrs[i],
LI->getOwnershipQualifier(),
true /*supports unqualified*/);
ElementTmps.push_back(SubLI);
}
if (LI->getType().is<TupleType>())
return B.createTuple(LI->getLoc(), LI->getType(), ElementTmps);
return B.createStruct(LI->getLoc(), LI->getType(), ElementTmps);
}
//===----------------------------------------------------------------------===//
// ElementUseCollector Implementation
//===----------------------------------------------------------------------===//
namespace {
class ElementUseCollector {
SILModule &Module;
const PMOMemoryObjectInfo &TheMemory;
SmallVectorImpl<PMOMemoryUse> &Uses;
SmallVectorImpl<SILInstruction *> &Releases;
/// When walking the use list, if we index into a struct element, keep track
/// of this, so that any indexes into tuple subelements don't affect the
/// element we attribute an access to.
bool InStructSubElement = false;
public:
ElementUseCollector(const PMOMemoryObjectInfo &TheMemory,
SmallVectorImpl<PMOMemoryUse> &Uses,
SmallVectorImpl<SILInstruction *> &Releases)
: Module(TheMemory.MemoryInst->getModule()), TheMemory(TheMemory),
Uses(Uses), Releases(Releases) {}
/// This is the main entry point for the use walker. It collects uses from
/// the address and the refcount result of the allocation.
LLVM_NODISCARD bool collectFrom();
private:
LLVM_NODISCARD bool collectUses(SILValue Pointer);
LLVM_NODISCARD bool collectContainerUses(SILValue boxValue);
};
} // end anonymous namespace
bool ElementUseCollector::collectFrom() {
if (auto *abi = TheMemory.getContainer()) {
return collectContainerUses(abi);
}
return collectUses(TheMemory.getAddress());
}
bool ElementUseCollector::collectContainerUses(SILValue boxValue) {
assert(isa<AllocBoxInst>(boxValue) || isa<CopyValueInst>(boxValue));
for (auto *ui : boxValue->getUses()) {
auto *user = ui->getUser();
// dealloc_box deallocated a box containing uninitialized memory. This can
// not effect any value stored into the box.
if (isa<DeallocBoxInst>(user))
continue;
// Retaining the box doesn't effect the value inside the box.
if (isa<StrongRetainInst>(user) || isa<RetainValueInst>(user))
continue;
// Like retaining, copies do not effect the underlying value. We do need to
// recursively visit the copies users though.
if (auto *cvi = dyn_cast<CopyValueInst>(user)) {
if (!collectContainerUses(cvi))
return false;
continue;
}
// Since we are trying to promote loads/stores, any releases of the box are
// not considered uses of the underlying value due to:
//
// 1. If this is not the last release of the box, then the underlying value
// is not effected implying we do not add this value.
//
// 2. If this is the last release of the box, then the box's destruction
// will result in a release of the underlying value. If there are any
// loads/stores after this point, the behavior would be undefined so we can
// ignore this possibility.
//
// That being said, if we want to eliminate the box completely we need to
// know where the releases are so that we can release the value that would
// have been at +1 in the box at that time. So we add these to the Releases
// array.
//
// FIXME: Since we do not support promoting strong_release or release_value
// today this will cause the underlying allocation to never be
// eliminated. That should be implemented and fixed.
if (isa<StrongReleaseInst>(user) || isa<ReleaseValueInst>(user) ||
isa<DestroyValueInst>(user)) {
Releases.push_back(user);
continue;
}
if (auto *p = dyn_cast<ProjectBoxInst>(user)) {
if (!collectUses(p))
return false;
continue;
}
// Other uses of the container are considered escapes of the underlying
// value.
//
// This will cause the dataflow to stop propagating any information at the
// use block.
Uses.emplace_back(user, PMOUseKind::Escape);
}
return true;
}
bool ElementUseCollector::collectUses(SILValue Pointer) {
assert(Pointer->getType().isAddress() &&
"Walked through the pointer to the value?");
SILType PointeeType = Pointer->getType().getObjectType();
/// This keeps track of instructions in the use list that touch multiple tuple
/// elements and should be scalarized. This is done as a second phase to
/// avoid invalidating the use iterator.
///
SmallVector<SILInstruction *, 4> UsesToScalarize;
for (auto *UI : Pointer->getUses()) {
auto *User = UI->getUser();
// struct_element_addr P, #field indexes into the current element.
if (auto *seai = dyn_cast<StructElementAddrInst>(User)) {
// Generally, we set the "InStructSubElement" flag and recursively process
// the uses so that we know that we're looking at something within the
// current element.
llvm::SaveAndRestore<bool> X(InStructSubElement, true);
if (!collectUses(seai))
return false;
continue;
}
// Instructions that compute a subelement are handled by a helper.
if (auto *teai = dyn_cast<TupleElementAddrInst>(User)) {
if (!collectUses(teai))
return false;
continue;
}
// Look through begin_access.
if (auto *bai = dyn_cast<BeginAccessInst>(User)) {
if (!collectUses(bai))
return false;
continue;
}
// Ignore end_access.
if (isa<EndAccessInst>(User)) {
continue;
}
// Loads are a use of the value.
if (isa<LoadInst>(User)) {
if (PointeeType.is<TupleType>())
UsesToScalarize.push_back(User);
else
Uses.emplace_back(User, PMOUseKind::Load);
continue;
}
#define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
if (isa<Load##Name##Inst>(User)) { \
Uses.emplace_back(User, PMOUseKind::Load); \
continue; \
}
#include "swift/AST/ReferenceStorage.def"
// Stores *to* the allocation are writes.
if (isa<StoreInst>(User) && UI->getOperandNumber() == 1) {
if (PointeeType.is<TupleType>()) {
UsesToScalarize.push_back(User);
continue;
}
// Coming out of SILGen, we assume that raw stores are initializations,
// unless they have trivial type (which we classify as InitOrAssign).
auto Kind = ([&]() -> PMOUseKind {
if (PointeeType.isTrivial(User->getModule()))
return PMOUseKind::InitOrAssign;
return PMOUseKind::Initialization;
})();
Uses.emplace_back(User, Kind);
continue;
}
#define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
if (auto *SI = dyn_cast<Store##Name##Inst>(User)) { \
if (UI->getOperandNumber() == 1) { \
PMOUseKind Kind; \
if (SI->isInitializationOfDest()) \
Kind = PMOUseKind::Initialization; \
else \
Kind = PMOUseKind::Assign; \
Uses.emplace_back(User, Kind); \
continue; \
} \
}
#include "swift/AST/ReferenceStorage.def"
if (auto *CAI = dyn_cast<CopyAddrInst>(User)) {
// If this is a copy of a tuple, we should scalarize it so that we don't
// have an access that crosses elements.
if (PointeeType.is<TupleType>()) {
UsesToScalarize.push_back(CAI);
continue;
}
// If this is the source of the copy_addr, then this is a load. If it is
// the destination, then this is an unknown assignment. Note that we'll
// revisit this instruction and add it to Uses twice if it is both a load
// and store to the same aggregate.
//
// Inline constructor.
auto Kind = ([&]() -> PMOUseKind {
if (UI->getOperandNumber() == CopyAddrInst::Src)
return PMOUseKind::Load;
if (CAI->isInitializationOfDest())
return PMOUseKind::Initialization;
return PMOUseKind::Assign;
})();
Uses.emplace_back(User, Kind);
continue;
}
// The apply instruction does not capture the pointer when it is passed
// through 'inout' arguments or for indirect returns. InOut arguments are
// treated as uses and may-store's, but an indirect return is treated as a
// full store.
//
// Note that partial_apply instructions always close over their argument.
//
if (auto *Apply = dyn_cast<ApplyInst>(User)) {
auto substConv = Apply->getSubstCalleeConv();
unsigned ArgumentNumber = UI->getOperandNumber() - 1;
// If this is an out-parameter, it is like a store.
unsigned NumIndirectResults = substConv.getNumIndirectSILResults();
if (ArgumentNumber < NumIndirectResults) {
// We do not support initializing sub members. This is an old
// restriction from when this code was used by Definite
// Initialization. With proper code review, we can remove this, but for
// now, lets be conservative.
if (InStructSubElement) {
return false;
}
Uses.emplace_back(User, PMOUseKind::Initialization);
continue;
// Otherwise, adjust the argument index.
} else {
ArgumentNumber -= NumIndirectResults;
}
auto ParamConvention =
substConv.getParameters()[ArgumentNumber].getConvention();
switch (ParamConvention) {
case ParameterConvention::Direct_Owned:
case ParameterConvention::Direct_Unowned:
case ParameterConvention::Direct_Guaranteed:
llvm_unreachable("address value passed to indirect parameter");
// If this is an in-parameter, it is like a load.
case ParameterConvention::Indirect_In:
case ParameterConvention::Indirect_In_Constant:
case ParameterConvention::Indirect_In_Guaranteed:
Uses.emplace_back(User, PMOUseKind::IndirectIn);
continue;
// If this is an @inout parameter, it is like both a load and store.
case ParameterConvention::Indirect_Inout:
case ParameterConvention::Indirect_InoutAliasable: {
// If we're in the initializer for a struct, and this is a call to a
// mutating method, we model that as an escape of self. If an
// individual sub-member is passed as inout, then we model that as an
// inout use.
Uses.emplace_back(User, PMOUseKind::InOutUse);
continue;
}
}
llvm_unreachable("bad parameter convention");
}
// init_existential_addr is modeled as an initialization store.
if (isa<InitExistentialAddrInst>(User)) {
// init_existential_addr should not apply to struct subelements.
if (InStructSubElement) {
return false;
}
Uses.emplace_back(User, PMOUseKind::Initialization);
continue;
}
// open_existential_addr is a use of the protocol value,
// so it is modeled as a load.
if (isa<OpenExistentialAddrInst>(User)) {
Uses.emplace_back(User, PMOUseKind::Load);
// TODO: Is it safe to ignore all uses of the open_existential_addr?
continue;
}
// We model destroy_addr as a release of the entire value.
if (isa<DestroyAddrInst>(User)) {
Releases.push_back(User);
continue;
}
if (isa<DeallocStackInst>(User)) {
continue;
}
// Sanitizer instrumentation is not user visible, so it should not
// count as a use and must not affect compile-time diagnostics.
if (isSanitizerInstrumentation(User))
continue;
// Otherwise, the use is something complicated, it escapes.
Uses.emplace_back(User, PMOUseKind::Escape);
}
// Now that we've walked all of the immediate uses, scalarize any operations
// working on tuples if we need to for canonicalization or analysis reasons.
if (!UsesToScalarize.empty()) {
SILInstruction *PointerInst = Pointer->getDefiningInstruction();
SmallVector<SILValue, 4> ElementAddrs;
SILBuilderWithScope AddrBuilder(++SILBasicBlock::iterator(PointerInst),
PointerInst);
getScalarizedElementAddresses(Pointer, AddrBuilder, PointerInst->getLoc(),
ElementAddrs);
SmallVector<SILValue, 4> ElementTmps;
for (auto *User : UsesToScalarize) {
ElementTmps.clear();
LLVM_DEBUG(llvm::errs() << " *** Scalarizing: " << *User << "\n");
// Scalarize LoadInst
if (auto *LI = dyn_cast<LoadInst>(User)) {
SILValue Result = scalarizeLoad(LI, ElementAddrs);
LI->replaceAllUsesWith(Result);
LI->eraseFromParent();
continue;
}
// Scalarize StoreInst
if (auto *SI = dyn_cast<StoreInst>(User)) {
SILBuilderWithScope B(User, SI);
B.emitDestructureValueOperation(
SI->getLoc(), SI->getSrc(),
[&](unsigned index, SILValue v) { ElementTmps.push_back(v); });
for (unsigned i = 0, e = ElementAddrs.size(); i != e; ++i)
B.createTrivialStoreOr(SI->getLoc(), ElementTmps[i], ElementAddrs[i],
SI->getOwnershipQualifier(),
true /*supports unqualified*/);
SI->eraseFromParent();
continue;
}
// Scalarize CopyAddrInst.
auto *CAI = cast<CopyAddrInst>(User);
SILBuilderWithScope B(User, CAI);
// Determine if this is a copy *from* or *to* "Pointer".
if (CAI->getSrc() == Pointer) {
// Copy from pointer.
getScalarizedElementAddresses(CAI->getDest(), B, CAI->getLoc(),
ElementTmps);
for (unsigned i = 0, e = ElementAddrs.size(); i != e; ++i)
B.createCopyAddr(CAI->getLoc(), ElementAddrs[i], ElementTmps[i],
CAI->isTakeOfSrc(), CAI->isInitializationOfDest());
} else {
getScalarizedElementAddresses(CAI->getSrc(), B, CAI->getLoc(),
ElementTmps);
for (unsigned i = 0, e = ElementAddrs.size(); i != e; ++i)
B.createCopyAddr(CAI->getLoc(), ElementTmps[i], ElementAddrs[i],
CAI->isTakeOfSrc(), CAI->isInitializationOfDest());
}
CAI->eraseFromParent();
}
// Now that we've scalarized some stuff, recurse down into the newly created
// element address computations to recursively process it. This can cause
// further scalarization.
if (llvm::any_of(ElementAddrs, [&](SILValue v) {
return !collectUses(cast<TupleElementAddrInst>(v));
})) {
return false;
}
}
return true;
}
/// collectPMOElementUsesFrom - Analyze all uses of the specified allocation
/// instruction (alloc_box, alloc_stack or mark_uninitialized), classifying them
/// and storing the information found into the Uses and Releases lists.
bool swift::collectPMOElementUsesFrom(
const PMOMemoryObjectInfo &MemoryInfo, SmallVectorImpl<PMOMemoryUse> &Uses,
SmallVectorImpl<SILInstruction *> &Releases) {
return ElementUseCollector(MemoryInfo, Uses, Releases).collectFrom();
}