mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
990 lines
38 KiB
C++
990 lines
38 KiB
C++
//===--- MemoryLifetimeVerifier.cpp ---------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#define DEBUG_TYPE "sil-memory-lifetime-verifier"
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/SIL/ApplySite.h"
|
|
#include "swift/SIL/BasicBlockDatastructures.h"
|
|
#include "swift/SIL/BasicBlockUtils.h"
|
|
#include "swift/SIL/BitDataflow.h"
|
|
#include "swift/SIL/CalleeCache.h"
|
|
#include "swift/SIL/MemoryLocations.h"
|
|
#include "swift/SIL/SILBasicBlock.h"
|
|
#include "swift/SIL/SILFunction.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
|
|
using namespace swift;
|
|
|
|
llvm::cl::opt<bool> DontAbortOnMemoryLifetimeErrors(
|
|
"dont-abort-on-memory-lifetime-errors",
|
|
llvm::cl::desc("Don't abort compilation if the memory lifetime checker "
|
|
"detects an error."));
|
|
|
|
namespace {
|
|
|
|
/// A utility for verifying memory lifetime.
|
|
///
|
|
/// The MemoryLifetime utility checks the lifetime of memory locations.
|
|
/// This is limited to memory locations which can be handled by
|
|
/// `MemoryLocations`.
|
|
class MemoryLifetimeVerifier {
|
|
|
|
using Bits = MemoryLocations::Bits;
|
|
using Location = MemoryLocations::Location;
|
|
using BlockState = BitDataflow::BlockState;
|
|
|
|
SILFunction *function;
|
|
CalleeCache *calleeCache;
|
|
DeadEndBlocks *deadEndBlocks;
|
|
MemoryLocations locations;
|
|
|
|
/// alloc_stack memory locations which are used for store_borrow.
|
|
Bits storeBorrowLocations;
|
|
|
|
/// Returns true if the location \p locIdx can be proven to hold a
|
|
/// hold a trivial value (e.g. non-payload case or thin function) at
|
|
/// \p atInst.
|
|
bool isValueTrivialAt(int locIdx, SILInstruction *atInst);
|
|
|
|
/// Returns true if an instruction in the range between \p start and \p end
|
|
/// stores a trivial enum case into the enum location \p loc.
|
|
bool storesTrivialValue(int locIdx,
|
|
SILBasicBlock::reverse_iterator start,
|
|
SILBasicBlock::reverse_iterator end);
|
|
|
|
/// Returns true if \p block contains a `switch_enum` or `switch_enum_addr`
|
|
/// and \p succ is a a successor block for a enum case with no payload or a
|
|
/// trivial payload.
|
|
bool isTrivialEnumSuccessor(SILBasicBlock *block, SILBasicBlock *succ,
|
|
int locIdx);
|
|
|
|
/// Issue an error for a memory location.
|
|
void reportError(const Twine &complaint, int locationIdx,
|
|
SILInstruction *where);
|
|
|
|
/// Issue an error if any bit in \p wrongBits is set.
|
|
void require(const Bits &wrongBits, const Twine &complaint,
|
|
SILInstruction *where, bool excludeTrivialValues = false);
|
|
|
|
/// Require that all the subLocation bits of the location, associated with
|
|
/// \p addr, are clear in \p bits.
|
|
void requireBitsClear(const Bits &bits, SILValue addr, SILInstruction *where);
|
|
|
|
/// Require that all the subLocation bits of the location, associated with
|
|
/// \p addr, are set in \p bits.
|
|
void requireBitsSet(const Bits &bits, SILValue addr, SILInstruction *where);
|
|
|
|
void requireBitsSetForArgument(const Bits &bits, Operand *argOp);
|
|
|
|
bool applyMayRead(Operand *argOp, SILValue addr);
|
|
|
|
bool isStoreBorrowLocation(SILValue addr) {
|
|
auto *loc = locations.getLocation(addr);
|
|
return loc && storeBorrowLocations.anyCommon(loc->subLocations);
|
|
}
|
|
|
|
/// Require that the location of addr is not an alloc_stack used for a
|
|
/// store_borrow.
|
|
void requireNoStoreBorrowLocation(SILValue addr, SILInstruction *where);
|
|
|
|
/// Register the destination address of a store_borrow as borrowed location.
|
|
void registerStoreBorrowLocation(SILValue addr);
|
|
|
|
/// Registers all store_borrow instructions in a block.
|
|
void registerStoreBorrowsInBlock(SILBasicBlock *block);
|
|
|
|
/// Handles locations of the predecessor's terminator, which are only valid
|
|
/// in \p block.
|
|
/// Example: @out results of try_apply. They are only valid in the
|
|
/// normal-block, but not in the throw-block.
|
|
void setBitsOfPredecessor(Bits &genSet, Bits &killSet, SILBasicBlock *block);
|
|
|
|
/// Initializes the data flow bits sets in the block states for all blocks.
|
|
void initDataflow(BitDataflow &dataFlow);
|
|
|
|
/// Initializes the data flow bits sets in the block state for a single block.
|
|
void initDataflowInBlock(SILBasicBlock *block, BlockState &state);
|
|
|
|
/// Helper function to set bits for function arguments and returns.
|
|
void setFuncOperandBits(BlockState &state, Operand &op,
|
|
SILArgumentConvention convention,
|
|
bool isTryApply);
|
|
|
|
/// Perform all checks in the function after the data flow has been computed.
|
|
void checkFunction(BitDataflow &dataFlow);
|
|
|
|
/// Check all instructions in \p block, starting with \p bits as entry set.
|
|
void checkBlock(SILBasicBlock *block, Bits &bits);
|
|
|
|
/// Check a function argument against the current live \p bits at the function
|
|
/// call.
|
|
void checkFuncArgument(Bits &bits, Operand &argumentOp,
|
|
SILArgumentConvention argumentConvention,
|
|
SILInstruction *applyInst);
|
|
|
|
// Utility functions for setting and clearing gen- and kill-bits.
|
|
|
|
void genBits(BitDataflow::BlockState &blockState, SILValue addr) {
|
|
locations.genBits(blockState.genSet, blockState.killSet, addr);
|
|
}
|
|
|
|
void killBits(BitDataflow::BlockState &blockState, SILValue addr) {
|
|
locations.killBits(blockState.genSet, blockState.killSet, addr);
|
|
}
|
|
|
|
public:
|
|
MemoryLifetimeVerifier(SILFunction *function, CalleeCache *calleeCache,
|
|
DeadEndBlocks *deadEndBlocks)
|
|
: function(function), calleeCache(calleeCache),
|
|
deadEndBlocks(deadEndBlocks),
|
|
locations(/*handleNonTrivialProjections*/ true,
|
|
/*handleTrivialLocations*/ true) {}
|
|
|
|
/// The main entry point to verify the lifetime of all memory locations in
|
|
/// the function.
|
|
void verify();
|
|
};
|
|
|
|
bool MemoryLifetimeVerifier::isValueTrivialAt(int locIdx,
|
|
SILInstruction *atInst) {
|
|
SILBasicBlock *startBlock = atInst->getParent();
|
|
|
|
// Start at atInst an walk up the control flow.
|
|
BasicBlockWorklist worklist(startBlock);
|
|
while (SILBasicBlock *block = worklist.pop()) {
|
|
auto start = (block == atInst->getParent() ? atInst->getReverseIterator()
|
|
: block->rbegin());
|
|
if (storesTrivialValue(locIdx, start, block->rend())) {
|
|
// Stop at trivial stores to the enum.
|
|
continue;
|
|
}
|
|
if (block == function->getEntryBlock()) {
|
|
return false;
|
|
}
|
|
for (SILBasicBlock *pred : block->getPredecessorBlocks()) {
|
|
// Stop walking to the predecessor if block is a non-payload successor
|
|
// of a switch_enum/switch_enum_addr.
|
|
if (!isTrivialEnumSuccessor(pred, block, locIdx))
|
|
worklist.pushIfNotVisited(pred);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool isTrivialEnumElem(EnumElementDecl *elem, SILType enumType,
|
|
SILFunction *function) {
|
|
return !elem->hasAssociatedValues() ||
|
|
enumType.getEnumElementType(elem, function).isTrivial(*function);
|
|
}
|
|
|
|
static bool injectsNoPayloadCase(InjectEnumAddrInst *IEAI) {
|
|
if (!IEAI->getElement()->hasAssociatedValues())
|
|
return true;
|
|
SILType enumType = IEAI->getOperand()->getType();
|
|
SILFunction *function = IEAI->getFunction();
|
|
SILType elemType = enumType.getEnumElementType(IEAI->getElement(), function);
|
|
// Handle empty types (e.g. the empty tuple) as no-payload.
|
|
return elemType.isEmpty(*function);
|
|
}
|
|
|
|
bool MemoryLifetimeVerifier::storesTrivialValue(int locIdx,
|
|
SILBasicBlock::reverse_iterator start,
|
|
SILBasicBlock::reverse_iterator end) {
|
|
for (SILInstruction &inst : make_range(start, end)) {
|
|
if (auto *IEI = dyn_cast<InjectEnumAddrInst>(&inst)) {
|
|
const Location *loc = locations.getLocation(IEI->getOperand());
|
|
if (loc && loc->isSubLocation(locIdx))
|
|
return isTrivialEnumElem(IEI->getElement(),
|
|
IEI->getOperand()->getType(),
|
|
function);
|
|
}
|
|
if (auto *SI = dyn_cast<StoreInst>(&inst)) {
|
|
const Location *loc = locations.getLocation(SI->getDest());
|
|
if (loc && loc->isSubLocation(locIdx)) {
|
|
auto ty = SI->getSrc()->getType();
|
|
if (ty.isOrHasEnum() || ty.isFunction()) {
|
|
return
|
|
SI->getOwnershipQualifier() == StoreOwnershipQualifier::Trivial;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MemoryLifetimeVerifier::isTrivialEnumSuccessor(SILBasicBlock *block,
|
|
SILBasicBlock *succ, int locIdx) {
|
|
TermInst *term = block->getTerminator();
|
|
NullablePtr<EnumElementDecl> elem;
|
|
SILType enumTy;
|
|
if (auto *switchEnum = dyn_cast<SwitchEnumInst>(term)) {
|
|
elem = switchEnum->getUniqueCaseForDestination(succ);
|
|
enumTy = switchEnum->getOperand()->getType();
|
|
} else if (auto *switchEnumAddr = dyn_cast<SwitchEnumAddrInst>(term)) {
|
|
elem = switchEnumAddr->getUniqueCaseForDestination(succ);
|
|
enumTy = switchEnumAddr->getOperand()->getType();
|
|
} else if (auto *switchValue = dyn_cast<SwitchValueInst>(term)) {
|
|
auto destCase = switchValue->getUniqueCaseForDestination(succ);
|
|
assert(destCase.has_value());
|
|
auto caseValue =
|
|
cast<IntegerLiteralInst>(switchValue->getCase(*destCase).first);
|
|
auto testValue = dyn_cast<IntegerLiteralInst>(switchValue->getOperand());
|
|
return testValue ? testValue->getValue() != caseValue->getValue() : true;
|
|
} else {
|
|
return false;
|
|
}
|
|
// The conservative default (if we cannot figure out the element) is to
|
|
// assume that it's a trivial element.
|
|
if (!elem)
|
|
return true;
|
|
return isTrivialEnumElem(elem.get(), enumTy, function);
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::reportError(const Twine &complaint,
|
|
int locationIdx, SILInstruction *where) {
|
|
llvm::errs() << "SIL memory lifetime failure in @" << function->getName()
|
|
<< ": " << complaint << '\n';
|
|
if (locationIdx >= 0) {
|
|
llvm::errs() << "memory location: "
|
|
<< locations.getLocation(locationIdx)->representativeValue;
|
|
}
|
|
llvm::errs() << "at instruction: " << *where << '\n';
|
|
|
|
if (DontAbortOnMemoryLifetimeErrors)
|
|
return;
|
|
|
|
ABORT([&](auto &out) {
|
|
out << "in function:\n";
|
|
function->print(out);
|
|
});
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::require(const Bits &wrongBits,
|
|
const Twine &complaint, SILInstruction *where,
|
|
bool excludeTrivialEnums) {
|
|
for (int errorLocIdx = wrongBits.find_first(); errorLocIdx >= 0;
|
|
errorLocIdx = wrongBits.find_next(errorLocIdx)) {
|
|
if (!excludeTrivialEnums || !isValueTrivialAt(errorLocIdx, where))
|
|
reportError(complaint, errorLocIdx, where);
|
|
}
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::requireBitsClear(const Bits &bits, SILValue addr,
|
|
SILInstruction *where) {
|
|
if (auto *loc = locations.getLocation(addr)) {
|
|
require(bits & loc->subLocations, "memory is initialized, but shouldn't be",
|
|
where, /*excludeTrivialEnums*/ true);
|
|
}
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::requireBitsSet(const Bits &bits, SILValue addr,
|
|
SILInstruction *where) {
|
|
if (auto *loc = locations.getLocation(addr)) {
|
|
require(~bits & loc->subLocations,
|
|
"memory is not initialized, but should be", where);
|
|
}
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::requireBitsSetForArgument(const Bits &bits, Operand *argOp) {
|
|
if (auto *loc = locations.getLocation(argOp->get())) {
|
|
Bits missingBits = ~bits & loc->subLocations;
|
|
for (int errorLocIdx = missingBits.find_first(); errorLocIdx >= 0;
|
|
errorLocIdx = missingBits.find_next(errorLocIdx)) {
|
|
auto *errorLoc = locations.getLocation(errorLocIdx);
|
|
|
|
if (applyMayRead(argOp, errorLoc->representativeValue)) {
|
|
reportError("memory is not initialized, but should be",
|
|
errorLocIdx, argOp->getUser());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MemoryLifetimeVerifier::applyMayRead(Operand *argOp, SILValue addr) {
|
|
// Conservatively assume that a partial_apply does _not_ read an argument.
|
|
if (isa<PartialApplyInst>(argOp->getUser()))
|
|
return false;
|
|
|
|
FullApplySite as(argOp->getUser());
|
|
CalleeList callees;
|
|
if (calleeCache) {
|
|
callees = calleeCache->getCalleeList(as);
|
|
if (callees.isIncomplete())
|
|
return true;
|
|
} else if (auto *callee = as.getReferencedFunctionOrNull()) {
|
|
callees = CalleeList(callee);
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
for (SILFunction *callee : callees) {
|
|
if (callee->argumentMayRead(argOp, addr))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::requireNoStoreBorrowLocation(
|
|
SILValue addr, SILInstruction *where) {
|
|
if (isStoreBorrowLocation(addr)) {
|
|
reportError("store-borrow location cannot be written",
|
|
locations.getLocationIdx(addr), where);
|
|
}
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::registerStoreBorrowLocation(SILValue addr) {
|
|
if (auto *loc = locations.getLocation(addr)) {
|
|
storeBorrowLocations.resize(locations.getNumLocations());
|
|
storeBorrowLocations |= loc->subLocations;
|
|
}
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::registerStoreBorrowsInBlock(SILBasicBlock *block) {
|
|
for (SILInstruction &inst : *block) {
|
|
if (auto *sbi = dyn_cast<StoreBorrowInst>(&inst))
|
|
registerStoreBorrowLocation(sbi->getDest());
|
|
}
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::initDataflow(BitDataflow &dataFlow) {
|
|
// Initialize the entry and exit sets to all-bits-set. Except for the function
|
|
// entry.
|
|
for (auto bs : dataFlow) {
|
|
if (&bs.block == function->getEntryBlock()) {
|
|
bs.data.entrySet.reset();
|
|
for (SILArgument *arg : function->getArguments()) {
|
|
SILFunctionArgument *funcArg = cast<SILFunctionArgument>(arg);
|
|
if (funcArg->getArgumentConvention() !=
|
|
SILArgumentConvention::Indirect_Out) {
|
|
locations.setBits(bs.data.entrySet, arg);
|
|
}
|
|
}
|
|
} else {
|
|
bs.data.entrySet.set();
|
|
}
|
|
bs.data.exitSet.set();
|
|
|
|
// Anything weird can happen in unreachable blocks. So just ignore them.
|
|
// Note: while solving the dataflow, unreachable blocks are implicitly
|
|
// ignored, because their entry/exit sets are all-ones and their gen/kill
|
|
// sets are all-zeroes.
|
|
if (bs.data.reachableFromEntry)
|
|
initDataflowInBlock(&bs.block, bs.data);
|
|
}
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::initDataflowInBlock(SILBasicBlock *block,
|
|
BlockState &state) {
|
|
// Initialize the genSet with special cases, like the @out results of an
|
|
// try_apply in the predecessor block.
|
|
setBitsOfPredecessor(state.genSet, state.killSet, block);
|
|
|
|
for (SILInstruction &I : *block) {
|
|
switch (I.getKind()) {
|
|
case SILInstructionKind::LoadInst: {
|
|
auto *LI = cast<LoadInst>(&I);
|
|
switch (LI->getOwnershipQualifier()) {
|
|
case LoadOwnershipQualifier::Take:
|
|
killBits(state, LI->getOperand());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case SILInstructionKind::StoreInst:
|
|
genBits(state, cast<StoreInst>(&I)->getDest());
|
|
break;
|
|
case SILInstructionKind::StoreBorrowInst: {
|
|
SILValue destAddr = cast<StoreBorrowInst>(&I)->getDest();
|
|
genBits(state, destAddr);
|
|
registerStoreBorrowLocation(destAddr);
|
|
break;
|
|
}
|
|
case SILInstructionKind::CopyAddrInst: {
|
|
auto *CAI = cast<CopyAddrInst>(&I);
|
|
if (CAI->isTakeOfSrc())
|
|
killBits(state, CAI->getSrc());
|
|
genBits(state, CAI->getDest());
|
|
break;
|
|
}
|
|
case SILInstructionKind::MarkUnresolvedMoveAddrInst: {
|
|
auto *MMAI = cast<MarkUnresolvedMoveAddrInst>(&I);
|
|
// We do not treat the move addr inst as invalidating its src since we
|
|
// are going to prove that we do not inappropriately reuse the memory
|
|
// later.
|
|
genBits(state, MMAI->getDest());
|
|
break;
|
|
}
|
|
case SILInstructionKind::InjectEnumAddrInst: {
|
|
auto *IEAI = cast<InjectEnumAddrInst>(&I);
|
|
int enumIdx = locations.getLocationIdx(IEAI->getOperand());
|
|
if (enumIdx >= 0 && injectsNoPayloadCase(IEAI)) {
|
|
// This is a bit tricky: an injected no-payload case means that the
|
|
// "full" enum is initialized. So, for the purpose of dataflow, we
|
|
// treat it like a full initialization of the payload data.
|
|
genBits(state, IEAI->getOperand());
|
|
}
|
|
break;
|
|
}
|
|
case SILInstructionKind::EndBorrowInst: {
|
|
auto *ebi = cast<EndBorrowInst>(&I);
|
|
if (auto *sbi = dyn_cast<StoreBorrowInst>(ebi->getOperand())) {
|
|
killBits(state, sbi->getDest());
|
|
}
|
|
break;
|
|
}
|
|
case SILInstructionKind::TupleAddrConstructorInst: {
|
|
auto *taci = cast<TupleAddrConstructorInst>(&I);
|
|
for (SILValue elt : taci->getElements()) {
|
|
if (elt->getType().isAddress())
|
|
killBits(state, elt);
|
|
}
|
|
genBits(state, taci->getDest());
|
|
break;
|
|
}
|
|
case SILInstructionKind::DestroyAddrInst:
|
|
case SILInstructionKind::DeallocStackInst:
|
|
killBits(state, I.getOperand(0));
|
|
break;
|
|
case SILInstructionKind::UncheckedRefCastAddrInst:
|
|
case SILInstructionKind::UnconditionalCheckedCastAddrInst: {
|
|
SILValue src = I.getOperand(CopyLikeInstruction::Src);
|
|
SILValue dest = I.getOperand(CopyLikeInstruction::Dest);
|
|
killBits(state, src);
|
|
genBits(state, dest);
|
|
break;
|
|
}
|
|
case SILInstructionKind::PartialApplyInst:
|
|
case SILInstructionKind::ApplyInst:
|
|
case SILInstructionKind::TryApplyInst: {
|
|
ApplySite AS(&I);
|
|
for (Operand &op : I.getAllOperands()) {
|
|
if (AS.isArgumentOperand(op)) {
|
|
setFuncOperandBits(state, op, AS.getCaptureConvention(op),
|
|
isa<TryApplyInst>(&I));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SILInstructionKind::BeginApplyInst: {
|
|
auto *BAI = cast<BeginApplyInst>(&I);
|
|
auto yieldedValues = BAI->getYieldedValues();
|
|
for (auto index : indices(yieldedValues)) {
|
|
auto fnType = BAI->getSubstCalleeType();
|
|
SILArgumentConvention argConv(
|
|
fnType->getYields()[index].getConvention());
|
|
if (argConv.isIndirectConvention()) {
|
|
genBits(state, yieldedValues[index]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SILInstructionKind::EndApplyInst:
|
|
case SILInstructionKind::AbortApplyInst: {
|
|
auto *BAI = [&]() {
|
|
if (auto *EAI = dyn_cast<EndApplyInst>(&I)) {
|
|
return EAI->getBeginApply();
|
|
}
|
|
auto *AAI = dyn_cast<AbortApplyInst>(&I);
|
|
return AAI->getBeginApply();
|
|
}();
|
|
auto yieldedValues = BAI->getYieldedValues();
|
|
for (auto index : indices(yieldedValues)) {
|
|
auto fnType = BAI->getSubstCalleeType();
|
|
SILArgumentConvention argConv(
|
|
fnType->getYields()[index].getConvention());
|
|
if (argConv.isIndirectConvention()) {
|
|
killBits(state, yieldedValues[index]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SILInstructionKind::YieldInst: {
|
|
auto *YI = cast<YieldInst>(&I);
|
|
for (Operand &op : YI->getAllOperands()) {
|
|
setFuncOperandBits(state, op, YI->getArgumentConventionForOperand(op),
|
|
/*isTryApply=*/ false);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::setBitsOfPredecessor(Bits &getSet, Bits &killSet,
|
|
SILBasicBlock *block) {
|
|
SILBasicBlock *pred = block->getSinglePredecessorBlock();
|
|
if (!pred)
|
|
return;
|
|
|
|
TermInst *term = pred->getTerminator();
|
|
if (auto *tai = dyn_cast<TryApplyInst>(term)) {
|
|
FullApplySite FAS(tai);
|
|
|
|
if (block == tai->getNormalBB()) {
|
|
// @out results of try_apply are only valid in the normal-block.
|
|
for (Operand &op : tai->getAllOperands()) {
|
|
if (FAS.isArgumentOperand(op) &&
|
|
FAS.isIndirectResultOperand(op)) {
|
|
locations.genBits(getSet, killSet, op.get());
|
|
}
|
|
}
|
|
} else {
|
|
// @error_indirect results of try_apply are only valid in the error-block.
|
|
assert(block == tai->getErrorBB());
|
|
for (Operand &op : tai->getAllOperands()) {
|
|
if (FAS.isArgumentOperand(op) &&
|
|
FAS.isIndirectErrorResultOperand(op)) {
|
|
locations.genBits(getSet, killSet, op.get());
|
|
}
|
|
}
|
|
}
|
|
} else if (auto *castInst = dyn_cast<CheckedCastAddrBranchInst>(term)) {
|
|
switch (castInst->getConsumptionKind()) {
|
|
case CastConsumptionKind::TakeAlways:
|
|
locations.killBits(getSet, killSet, castInst->getSrc());
|
|
break;
|
|
case CastConsumptionKind::TakeOnSuccess:
|
|
if (castInst->getSuccessBB() == block)
|
|
locations.killBits(getSet, killSet, castInst->getSrc());
|
|
break;
|
|
case CastConsumptionKind::CopyOnSuccess:
|
|
break;
|
|
case CastConsumptionKind::BorrowAlways:
|
|
llvm_unreachable("checked_cast_addr_br cannot have BorrowAlways");
|
|
}
|
|
if (castInst->getSuccessBB() == block)
|
|
locations.genBits(getSet, killSet, castInst->getDest());
|
|
}
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::setFuncOperandBits(BlockState &state, Operand &op,
|
|
SILArgumentConvention convention,
|
|
bool isTryApply) {
|
|
switch (convention) {
|
|
case SILArgumentConvention::Indirect_In_CXX:
|
|
case SILArgumentConvention::Indirect_In:
|
|
killBits(state, op.get());
|
|
break;
|
|
case SILArgumentConvention::Indirect_Out:
|
|
// try_apply is special, because an @out result is only initialized
|
|
// in the normal-block, but not in the throw-block.
|
|
// We handle the @out result of try_apply in setBitsOfPredecessor.
|
|
if (!isTryApply)
|
|
genBits(state, op.get());
|
|
break;
|
|
case SILArgumentConvention::Indirect_In_Guaranteed:
|
|
case SILArgumentConvention::Indirect_Inout:
|
|
case SILArgumentConvention::Indirect_InoutAliasable:
|
|
case SILArgumentConvention::Direct_Owned:
|
|
case SILArgumentConvention::Direct_Unowned:
|
|
case SILArgumentConvention::Direct_Guaranteed:
|
|
case SILArgumentConvention::Pack_Inout:
|
|
case SILArgumentConvention::Pack_Out:
|
|
case SILArgumentConvention::Pack_Guaranteed:
|
|
case SILArgumentConvention::Pack_Owned:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::checkFunction(BitDataflow &dataFlow) {
|
|
|
|
// Collect the bits which we require to be set at function exits.
|
|
Bits expectedReturnBits(locations.getNumLocations());
|
|
Bits expectedThrowBits(locations.getNumLocations());
|
|
for (SILArgument *arg : function->getArguments()) {
|
|
SILFunctionArgument *funcArg = cast<SILFunctionArgument>(arg);
|
|
switch (funcArg->getArgumentConvention()) {
|
|
case SILArgumentConvention::Indirect_Inout:
|
|
case SILArgumentConvention::Indirect_In_Guaranteed:
|
|
locations.setBits(expectedReturnBits, funcArg);
|
|
locations.setBits(expectedThrowBits, funcArg);
|
|
break;
|
|
case SILArgumentConvention::Indirect_Out:
|
|
if (funcArg->isIndirectErrorResult()) {
|
|
locations.setBits(expectedThrowBits, funcArg);
|
|
} else {
|
|
assert(funcArg->isIndirectResult());
|
|
locations.setBits(expectedReturnBits, funcArg);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
const Bits &nonTrivialLocations = locations.getNonTrivialLocations();
|
|
Bits bits(locations.getNumLocations());
|
|
for (auto bs : dataFlow) {
|
|
if (!bs.data.reachableFromEntry || !bs.data.exitReachable())
|
|
continue;
|
|
|
|
// Check all instructions in the block.
|
|
bits = bs.data.entrySet;
|
|
checkBlock(&bs.block, bits);
|
|
|
|
// Check if there is a mismatch in location lifetime at the merge point.
|
|
for (SILBasicBlock *pred : bs.block.getPredecessorBlocks()) {
|
|
BlockState &predState = dataFlow[pred];
|
|
if (predState.reachableFromEntry) {
|
|
require((bs.data.entrySet ^ predState.exitSet) & nonTrivialLocations,
|
|
"lifetime mismatch in predecessors", pred->getTerminator(),
|
|
/*excludeTrivialEnums*/ true);
|
|
}
|
|
}
|
|
|
|
// Check the bits at function exit.
|
|
TermInst *term = bs.block.getTerminator();
|
|
assert(bits == bs.data.exitSet || isa<TryApplyInst>(term));
|
|
switch (term->getKind()) {
|
|
case SILInstructionKind::ReturnInst:
|
|
case SILInstructionKind::UnwindInst:
|
|
require(expectedReturnBits & ~bs.data.exitSet,
|
|
"indirect argument is not alive at function return", term);
|
|
require(bs.data.exitSet & ~expectedReturnBits & nonTrivialLocations,
|
|
"memory is initialized at function return but shouldn't be",
|
|
term,
|
|
/*excludeTrivialEnums*/ true);
|
|
break;
|
|
case SILInstructionKind::ThrowInst:
|
|
require(expectedThrowBits & ~bs.data.exitSet,
|
|
"indirect argument is not alive at throw", term);
|
|
require(bs.data.exitSet & ~expectedThrowBits & nonTrivialLocations,
|
|
"memory is initialized at throw but shouldn't be", term,
|
|
/*excludeTrivialEnums*/ true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::checkBlock(SILBasicBlock *block, Bits &bits) {
|
|
setBitsOfPredecessor(bits, bits, block);
|
|
const Bits &nonTrivialLocations = locations.getNonTrivialLocations();
|
|
|
|
for (SILInstruction &I : *block) {
|
|
switch (I.getKind()) {
|
|
case SILInstructionKind::LoadInst: {
|
|
auto *LI = cast<LoadInst>(&I);
|
|
requireBitsSet(bits, LI->getOperand(), &I);
|
|
switch (LI->getOwnershipQualifier()) {
|
|
case LoadOwnershipQualifier::Take:
|
|
locations.clearBits(bits, LI->getOperand());
|
|
requireNoStoreBorrowLocation(LI->getOperand(), &I);
|
|
break;
|
|
case LoadOwnershipQualifier::Copy:
|
|
case LoadOwnershipQualifier::Trivial:
|
|
break;
|
|
case LoadOwnershipQualifier::Unqualified:
|
|
llvm_unreachable("unqualified load shouldn't be in ownership SIL");
|
|
}
|
|
break;
|
|
}
|
|
case SILInstructionKind::StoreInst: {
|
|
auto *SI = cast<StoreInst>(&I);
|
|
switch (SI->getOwnershipQualifier()) {
|
|
case StoreOwnershipQualifier::Init:
|
|
requireBitsClear(bits & nonTrivialLocations, SI->getDest(), &I);
|
|
locations.setBits(bits, SI->getDest());
|
|
break;
|
|
case StoreOwnershipQualifier::Assign:
|
|
requireBitsSet(bits | ~nonTrivialLocations, SI->getDest(), &I);
|
|
break;
|
|
case StoreOwnershipQualifier::Trivial:
|
|
locations.setBits(bits, SI->getDest());
|
|
break;
|
|
case StoreOwnershipQualifier::Unqualified:
|
|
llvm_unreachable("unqualified store shouldn't be in ownership SIL");
|
|
}
|
|
requireNoStoreBorrowLocation(SI->getDest(), &I);
|
|
break;
|
|
}
|
|
case SILInstructionKind::StoreBorrowInst: {
|
|
SILValue destAddr = cast<StoreBorrowInst>(&I)->getDest();
|
|
locations.setBits(bits, destAddr);
|
|
break;
|
|
}
|
|
case SILInstructionKind::CopyAddrInst: {
|
|
auto *CAI = cast<CopyAddrInst>(&I);
|
|
requireBitsSet(bits, CAI->getSrc(), &I);
|
|
if (CAI->isTakeOfSrc()) {
|
|
locations.clearBits(bits, CAI->getSrc());
|
|
requireNoStoreBorrowLocation(CAI->getSrc(), &I);
|
|
}
|
|
if (CAI->isInitializationOfDest()) {
|
|
requireBitsClear(bits & nonTrivialLocations, CAI->getDest(), &I);
|
|
} else {
|
|
requireBitsSet(bits | ~nonTrivialLocations, CAI->getDest(), &I);
|
|
}
|
|
locations.setBits(bits, CAI->getDest());
|
|
requireNoStoreBorrowLocation(CAI->getDest(), &I);
|
|
break;
|
|
}
|
|
case SILInstructionKind::InjectEnumAddrInst: {
|
|
auto *IEAI = cast<InjectEnumAddrInst>(&I);
|
|
int enumIdx = locations.getLocationIdx(IEAI->getOperand());
|
|
if (enumIdx >= 0) {
|
|
if (injectsNoPayloadCase(IEAI)) {
|
|
// Again, an injected no-payload case is treated like a "full"
|
|
// initialization. See initDataflowInBlock().
|
|
requireBitsClear(bits & nonTrivialLocations, IEAI->getOperand(), &I);
|
|
locations.setBits(bits, IEAI->getOperand());
|
|
} else {
|
|
requireBitsSet(bits, IEAI->getOperand(), &I);
|
|
}
|
|
}
|
|
requireNoStoreBorrowLocation(IEAI->getOperand(), &I);
|
|
break;
|
|
}
|
|
case SILInstructionKind::InitExistentialAddrInst:
|
|
case SILInstructionKind::InitEnumDataAddrInst: {
|
|
SILValue addr = I.getOperand(0);
|
|
requireBitsClear(bits & nonTrivialLocations, addr, &I);
|
|
requireNoStoreBorrowLocation(addr, &I);
|
|
break;
|
|
}
|
|
case SILInstructionKind::OpenExistentialAddrInst:
|
|
case SILInstructionKind::SelectEnumAddrInst:
|
|
case SILInstructionKind::ExistentialMetatypeInst:
|
|
case SILInstructionKind::ValueMetatypeInst:
|
|
case SILInstructionKind::IsUniqueInst:
|
|
case SILInstructionKind::FixLifetimeInst:
|
|
requireBitsSet(bits, I.getOperand(0), &I);
|
|
break;
|
|
case SILInstructionKind::DebugValueInst:
|
|
// We don't want to check `debug_value` instructions that
|
|
// are used to mark variable declarations (e.g. its SSA value is
|
|
// an alloc_stack), which don't have any `op_deref` in its
|
|
// di-expression, because that memory doesn't need to be initialized
|
|
// when `debug_value` is referencing it.
|
|
if (!DebugValueInst::hasAddrVal(&I))
|
|
requireBitsSet(bits, I.getOperand(0), &I);
|
|
break;
|
|
case SILInstructionKind::UncheckedTakeEnumDataAddrInst: {
|
|
// Note that despite the name, unchecked_take_enum_data_addr does _not_
|
|
// "take" the payload of the Swift.Optional enum. This is a terrible
|
|
// hack in SIL.
|
|
auto enumInst = cast<UncheckedTakeEnumDataAddrInst>(&I);
|
|
// For some enums, projecting the enum data requires masking out
|
|
// embedded tag bits, which invalidates the value as an enum.
|
|
if (enumInst->isDestructive()) {
|
|
SILValue enumAddr = enumInst->getOperand();
|
|
int enumIdx = locations.getLocationIdx(enumAddr);
|
|
if (enumIdx >= 0)
|
|
requireBitsSet(bits, enumAddr, &I);
|
|
requireNoStoreBorrowLocation(enumAddr, &I);
|
|
}
|
|
break;
|
|
}
|
|
case SILInstructionKind::DestroyAddrInst: {
|
|
SILValue opVal = cast<DestroyAddrInst>(&I)->getOperand();
|
|
requireBitsSet(bits | ~nonTrivialLocations, opVal, &I);
|
|
locations.clearBits(bits, opVal);
|
|
requireNoStoreBorrowLocation(opVal, &I);
|
|
break;
|
|
}
|
|
case SILInstructionKind::EndBorrowInst: {
|
|
auto *ebi = cast<EndBorrowInst>(&I);
|
|
if (auto *sbi = dyn_cast<StoreBorrowInst>(ebi->getOperand())) {
|
|
requireBitsSet(bits, sbi->getDest(), &I);
|
|
locations.clearBits(bits, sbi->getDest());
|
|
} else if (auto *lbi = dyn_cast<LoadBorrowInst>(ebi->getOperand())) {
|
|
if (!lbi->isUnchecked()) {
|
|
requireBitsSet(bits, lbi->getOperand(), &I);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SILInstructionKind::UncheckedRefCastAddrInst:
|
|
case SILInstructionKind::UnconditionalCheckedCastAddrInst: {
|
|
SILValue src = I.getOperand(CopyLikeInstruction::Src);
|
|
SILValue dest = I.getOperand(CopyLikeInstruction::Dest);
|
|
requireBitsSet(bits, src, &I);
|
|
locations.clearBits(bits, src);
|
|
requireBitsClear(bits & nonTrivialLocations, dest, &I);
|
|
locations.setBits(bits, dest);
|
|
requireNoStoreBorrowLocation(dest, &I);
|
|
break;
|
|
}
|
|
case SILInstructionKind::CheckedCastAddrBranchInst: {
|
|
auto *castInst = cast<CheckedCastAddrBranchInst>(&I);
|
|
requireBitsSet(bits, castInst->getSrc(), &I);
|
|
requireBitsClear(bits & nonTrivialLocations, castInst->getDest(), &I);
|
|
break;
|
|
}
|
|
case SILInstructionKind::PartialApplyInst:
|
|
case SILInstructionKind::ApplyInst:
|
|
case SILInstructionKind::TryApplyInst: {
|
|
ApplySite AS(&I);
|
|
for (Operand &op : I.getAllOperands()) {
|
|
if (AS.isArgumentOperand(op))
|
|
checkFuncArgument(bits, op, AS.getCaptureConvention(op), &I);
|
|
}
|
|
break;
|
|
}
|
|
case SILInstructionKind::BeginApplyInst: {
|
|
auto *BAI = cast<BeginApplyInst>(&I);
|
|
auto yieldedValues = BAI->getYieldedValues();
|
|
for (auto index : indices(yieldedValues)) {
|
|
auto fnType = BAI->getSubstCalleeType();
|
|
SILArgumentConvention argConv(
|
|
fnType->getYields()[index].getConvention());
|
|
if (argConv.isIndirectConvention()) {
|
|
requireBitsClear(bits, yieldedValues[index], &I);
|
|
locations.setBits(bits, yieldedValues[index]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SILInstructionKind::EndApplyInst:
|
|
case SILInstructionKind::AbortApplyInst: {
|
|
auto *BAI = [&]() {
|
|
if (auto *EAI = dyn_cast<EndApplyInst>(&I)) {
|
|
return EAI->getBeginApply();
|
|
}
|
|
auto *AAI = dyn_cast<AbortApplyInst>(&I);
|
|
return AAI->getBeginApply();
|
|
}();
|
|
auto yieldedValues = BAI->getYieldedValues();
|
|
for (auto index : indices(yieldedValues)) {
|
|
auto fnType = BAI->getSubstCalleeType();
|
|
SILArgumentConvention argConv(
|
|
fnType->getYields()[index].getConvention());
|
|
if (argConv.isIndirectConvention()) {
|
|
if (argConv.isInoutConvention() ||
|
|
argConv.isGuaranteedConventionInCaller()) {
|
|
requireBitsSet(bits | ~nonTrivialLocations, yieldedValues[index],
|
|
&I);
|
|
} else if (argConv.isOwnedConventionInCaller()) {
|
|
requireBitsClear(bits & nonTrivialLocations, yieldedValues[index],
|
|
&I);
|
|
}
|
|
locations.clearBits(bits, yieldedValues[index]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SILInstructionKind::YieldInst: {
|
|
auto *YI = cast<YieldInst>(&I);
|
|
for (Operand &op : YI->getAllOperands()) {
|
|
checkFuncArgument(bits, op, YI->getArgumentConventionForOperand(op),
|
|
&I);
|
|
}
|
|
break;
|
|
}
|
|
case SILInstructionKind::DeallocStackInst: {
|
|
SILValue opVal = cast<DeallocStackInst>(&I)->getOperand();
|
|
if (!deadEndBlocks->isDeadEnd(I.getParent())) {
|
|
// TODO: rdar://159311784: Maybe at some point the invariant will be
|
|
// enforced that values stored into addresses
|
|
// don't leak in dead-ends.
|
|
requireBitsClear(bits & nonTrivialLocations, opVal, &I);
|
|
}
|
|
// Needed to clear any bits of trivial locations (which are not required
|
|
// to be zero).
|
|
locations.clearBits(bits, opVal);
|
|
break;
|
|
}
|
|
case SILInstructionKind::MarkDependenceInst:
|
|
case SILInstructionKind::MarkDependenceAddrInst: {
|
|
auto mdi = MarkDependenceInstruction(&I);
|
|
if (mdi.getBase()->getType().isAddress() &&
|
|
// In case the mark_dependence is used for a closure it might be that the base
|
|
// is "self" in an initializer and "self" is not fully initialized, yet.
|
|
(!mdi.getType() || !mdi.getType().isFunction())) {
|
|
requireBitsSet(bits, mdi.getBase(), &I);
|
|
}
|
|
// TODO: check that the base operand is alive during the whole lifetime
|
|
// of the value operand. This requires treating all transitive uses of
|
|
// 'mdi' as uses of 'base' (including copies for non-Escapable types).
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::checkFuncArgument(Bits &bits, Operand &argumentOp,
|
|
SILArgumentConvention argumentConvention,
|
|
SILInstruction *applyInst) {
|
|
if (argumentConvention != SILArgumentConvention::Indirect_In_Guaranteed)
|
|
requireNoStoreBorrowLocation(argumentOp.get(), applyInst);
|
|
|
|
switch (argumentConvention) {
|
|
case SILArgumentConvention::Indirect_In_CXX:
|
|
case SILArgumentConvention::Indirect_In:
|
|
requireBitsSetForArgument(bits, &argumentOp);
|
|
locations.clearBits(bits, argumentOp.get());
|
|
break;
|
|
case SILArgumentConvention::Indirect_Out:
|
|
requireBitsClear(bits & locations.getNonTrivialLocations(),
|
|
argumentOp.get(), applyInst);
|
|
locations.setBits(bits, argumentOp.get());
|
|
break;
|
|
case SILArgumentConvention::Indirect_In_Guaranteed:
|
|
case SILArgumentConvention::Indirect_Inout:
|
|
requireBitsSetForArgument(bits, &argumentOp);
|
|
break;
|
|
case SILArgumentConvention::Indirect_InoutAliasable:
|
|
// We don't require any locations to be initialized for a partial_apply
|
|
// which takes an inout_aliasable argument. This is used for implicit
|
|
// closures (e.g. for the Bool '||' and '&&' operator arguments). Such
|
|
// closures capture the whole "self". When this is done in an initializer
|
|
// it can happen that not all fields of "self" are initialized, yet.
|
|
if (!isa<PartialApplyInst>(applyInst))
|
|
requireBitsSetForArgument(bits, &argumentOp);
|
|
break;
|
|
case SILArgumentConvention::Direct_Owned:
|
|
case SILArgumentConvention::Direct_Unowned:
|
|
case SILArgumentConvention::Direct_Guaranteed:
|
|
case SILArgumentConvention::Pack_Inout:
|
|
case SILArgumentConvention::Pack_Out:
|
|
case SILArgumentConvention::Pack_Guaranteed:
|
|
case SILArgumentConvention::Pack_Owned:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MemoryLifetimeVerifier::verify() {
|
|
// First step: handle memory locations which (potentially) span multiple
|
|
// blocks.
|
|
locations.analyzeLocations(function);
|
|
if (locations.getNumLocations() > 0) {
|
|
BitDataflow dataFlow(function, locations.getNumLocations());
|
|
dataFlow.entryReachabilityAnalysis();
|
|
dataFlow.exitReachableAnalysis();
|
|
initDataflow(dataFlow);
|
|
dataFlow.solveForwardWithIntersect();
|
|
checkFunction(dataFlow);
|
|
}
|
|
// Second step: handle single-block locations.
|
|
locations.handleSingleBlockLocations([this](SILBasicBlock *block) {
|
|
storeBorrowLocations.clear();
|
|
Bits bits(locations.getNumLocations());
|
|
registerStoreBorrowsInBlock(block);
|
|
checkBlock(block, bits);
|
|
});
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
void SILFunction::verifyMemoryLifetime(CalleeCache *calleeCache,
|
|
DeadEndBlocks *deadEndBlocks) {
|
|
MemoryLifetimeVerifier verifier(this, calleeCache, deadEndBlocks);
|
|
verifier.verify();
|
|
}
|