mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
653 lines
26 KiB
C++
653 lines
26 KiB
C++
//===--- ConsumeOperatorCopyableValuesChecker.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-consume-operator-copyable-values-checker"
|
|
|
|
#include "swift/AST/DiagnosticsSIL.h"
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/Basic/Defer.h"
|
|
#include "swift/Basic/GraphNodeWorklist.h"
|
|
#include "swift/SIL/BasicBlockBits.h"
|
|
#include "swift/SIL/BasicBlockDatastructures.h"
|
|
#include "swift/SIL/DebugUtils.h"
|
|
#include "swift/SIL/InstructionUtils.h"
|
|
#include "swift/SIL/OwnershipUtils.h"
|
|
#include "swift/SIL/SILArgument.h"
|
|
#include "swift/SIL/SILBuilder.h"
|
|
#include "swift/SIL/SILFunction.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/SILUndef.h"
|
|
#include "swift/SILOptimizer/Analysis/Analysis.h"
|
|
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
|
|
#include "swift/SILOptimizer/Analysis/ClosureScope.h"
|
|
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
|
|
#include "swift/SILOptimizer/Analysis/LoopAnalysis.h"
|
|
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
|
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
|
|
#include "swift/SILOptimizer/Utils/OSSACanonicalizeOwned.h"
|
|
|
|
using namespace swift;
|
|
|
|
static llvm::cl::opt<bool> DisableUnhandledMoveDiagnostic(
|
|
"sil-consume-operator-disable-unknown-move-diagnostic");
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Diagnostic Utilities
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
template <typename... T, typename... U>
|
|
static void diagnose(ASTContext &Context, SourceLoc loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
Context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Canonical Liveness
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
struct CheckerLivenessInfo {
|
|
GraphNodeWorklist<SILValue, 8> defUseWorklist;
|
|
llvm::SmallSetVector<Operand *, 8> consumingUse;
|
|
llvm::SmallSetVector<SILInstruction *, 8> nonLifetimeEndingUsesInLiveOut;
|
|
SmallVector<Operand *, 8> interiorPointerTransitiveUses;
|
|
BitfieldRef<DiagnosticPrunedLiveness> liveness;
|
|
|
|
CheckerLivenessInfo() : nonLifetimeEndingUsesInLiveOut() {}
|
|
|
|
void initDef(SILValue def) {
|
|
liveness->initializeDef(def);
|
|
defUseWorklist.insert(def);
|
|
}
|
|
|
|
/// Compute the liveness for any value currently in the defUseWorklist.
|
|
///
|
|
/// Returns false if we found any escapes. Returns true if no escape uses were
|
|
/// found. NOTE: Even if we return false, we still visit all uses and compute
|
|
/// liveness normally. We may just be missing uses through the escaping use.
|
|
bool compute();
|
|
|
|
void clear() {
|
|
defUseWorklist.clear();
|
|
consumingUse.clear();
|
|
interiorPointerTransitiveUses.clear();
|
|
nonLifetimeEndingUsesInLiveOut.clear();
|
|
}
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
bool CheckerLivenessInfo::compute() {
|
|
LLVM_DEBUG(llvm::dbgs() << "LivenessVisitor Begin!\n");
|
|
while (SILValue value = defUseWorklist.pop()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "New Value: " << value);
|
|
SWIFT_DEFER { LLVM_DEBUG(llvm::dbgs() << "Finished Value: " << value); };
|
|
|
|
for (Operand *use : value->getUses()) {
|
|
auto *user = use->getUser();
|
|
LLVM_DEBUG(llvm::dbgs() << " User: " << *user);
|
|
// Recurse through copies.
|
|
if (auto *copy = dyn_cast<CopyValueInst>(user)) {
|
|
LLVM_DEBUG(llvm::dbgs() << " Copy Value. Looking through it\n");
|
|
defUseWorklist.insert(copy);
|
|
continue;
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << " OperandOwnership: "
|
|
<< use->getOperandOwnership() << '\n');
|
|
switch (use->getOperandOwnership()) {
|
|
case OperandOwnership::NonUse:
|
|
break;
|
|
case OperandOwnership::TrivialUse:
|
|
llvm_unreachable("this operand cannot handle ownership");
|
|
|
|
// Conservatively treat a conversion to an unowned value as a pointer
|
|
// escape. Is it legal to canonicalize ForwardingUnowned?
|
|
case OperandOwnership::ForwardingUnowned:
|
|
case OperandOwnership::PointerEscape:
|
|
// This is an escape but it is up to the user to handle this, move
|
|
// checking stops here.
|
|
break;
|
|
case OperandOwnership::InstantaneousUse:
|
|
case OperandOwnership::UnownedInstantaneousUse:
|
|
case OperandOwnership::BitwiseEscape:
|
|
liveness->updateForUse(user, /*lifetimeEnding*/ false);
|
|
break;
|
|
case OperandOwnership::ForwardingConsume:
|
|
consumingUse.insert(use);
|
|
liveness->updateForUse(user, /*lifetimeEnding*/ true);
|
|
break;
|
|
case OperandOwnership::DestroyingConsume:
|
|
// destroy_value does not force pruned liveness (but store etc. does).
|
|
if (!isa<DestroyValueInst>(user)) {
|
|
liveness->updateForUse(user, /*lifetimeEnding*/ true);
|
|
}
|
|
consumingUse.insert(use);
|
|
break;
|
|
case OperandOwnership::Borrow: {
|
|
if (auto *bbi = dyn_cast<BeginBorrowInst>(user)) {
|
|
// If we have a lexical begin_borrow, we are going to check its uses
|
|
// separately and emit diagnostics for it. So we just need to add the
|
|
// liveness of the begin_borrow.
|
|
//
|
|
// NOTE: We know that semantically the use lexical lifetime must have
|
|
// a separate lifetime from the base lexical lifetime that we are
|
|
// processing. We do not want to include those uses as transitive uses
|
|
// of our base lexical lifetime. We just want to treat the formation
|
|
// of the new variable as a use. Thus we only include the begin_borrow
|
|
// itself as the use.
|
|
if (bbi->isLexical()) {
|
|
liveness->updateForUse(bbi, false /*lifetime ending*/);
|
|
} else {
|
|
// Otherwise, try to update liveness for a borrowing operand
|
|
// use. This will make it so that we add the end_borrows of the
|
|
// liveness use. If we have a reborrow here, we will bail.
|
|
if (liveness->updateForBorrowingOperand(use) !=
|
|
InnerBorrowKind::Contained) {
|
|
return false;
|
|
}
|
|
}
|
|
} else if (isa<StoreBorrowInst>(user)) {
|
|
if (liveness->updateForBorrowingOperand(use) !=
|
|
InnerBorrowKind::Contained) {
|
|
return false;
|
|
}
|
|
}
|
|
// FIXME: this ignores all other forms of Borrow ownership, such as
|
|
// partial_apply [onstack] and mark_dependence [nonescaping].
|
|
break;
|
|
}
|
|
case OperandOwnership::GuaranteedForwarding:
|
|
// A forwarding borrow is validated as part of its parent borrow. So
|
|
// just mark it as extending liveness and look through it.
|
|
liveness->updateForUse(user, /*lifetimeEnding*/ false);
|
|
ForwardingOperand(use).visitForwardedValues([&](SILValue result) {
|
|
if (SILArgument::isTerminatorResult(result)) {
|
|
return true;
|
|
}
|
|
if (result->getOwnershipKind() == OwnershipKind::Guaranteed)
|
|
defUseWorklist.insert(result);
|
|
return true;
|
|
});
|
|
break;
|
|
case OperandOwnership::InteriorPointer:
|
|
case OperandOwnership::AnyInteriorPointer: {
|
|
// An interior pointer user extends liveness until the end of the
|
|
// interior pointer section.
|
|
//
|
|
// TODO: We really should have all OperandOwnership::InteriorPointer
|
|
// instructions be valid to pass to InteriorPointerOperand. Some
|
|
// builtins do not do it today and it is probably a misuse of the
|
|
// system. That being said, lets do our best here.
|
|
if (auto operand = InteriorPointerOperand(use)) {
|
|
auto addrUseKind =
|
|
operand.findTransitiveUses(&interiorPointerTransitiveUses);
|
|
(void)addrUseKind;
|
|
while (!interiorPointerTransitiveUses.empty()) {
|
|
auto *addrUse = interiorPointerTransitiveUses.pop_back_val();
|
|
liveness->updateForUse(addrUse->getUser(),
|
|
/*lifetimeEnding*/ false);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case OperandOwnership::EndBorrow:
|
|
// Don't care about this use.
|
|
break;
|
|
case OperandOwnership::Reborrow:
|
|
// Reborrows do not occur this early in the pipeline.
|
|
llvm_unreachable(
|
|
"Reborrows do not occur until we optimize later in the pipeline");
|
|
}
|
|
}
|
|
}
|
|
|
|
// We succeeded if we reached this point since we handled all uses.
|
|
return true;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Main Pass
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
struct ConsumeOperatorCopyableValuesChecker {
|
|
SILFunction *fn;
|
|
CheckerLivenessInfo livenessInfo;
|
|
DominanceInfo *dominance;
|
|
InstructionDeleter deleter;
|
|
OSSACanonicalizeOwned canonicalizer;
|
|
|
|
ConsumeOperatorCopyableValuesChecker(
|
|
SILFunction *fn, DominanceInfo *dominance,
|
|
BasicCalleeAnalysis *calleeAnalysis,
|
|
DeadEndBlocksAnalysis *deadEndBlocksAnalysis)
|
|
: fn(fn), dominance(dominance),
|
|
canonicalizer(DontPruneDebugInsts,
|
|
MaximizeLifetime_t(!fn->shouldOptimize()), fn,
|
|
/*accessBlockAnalysis=*/nullptr, deadEndBlocksAnalysis,
|
|
dominance, calleeAnalysis, deleter) {}
|
|
|
|
bool check();
|
|
|
|
void emitDiagnosticForMove(SILValue borrowedValue,
|
|
StringRef borrowedValueName, MoveValueInst *mvi);
|
|
};
|
|
|
|
} // namespace
|
|
|
|
static SourceLoc getSourceLocFromValue(SILValue value) {
|
|
if (auto *defInst = value->getDefiningInstruction())
|
|
return defInst->getLoc().getSourceLoc();
|
|
if (auto *arg = dyn_cast<SILFunctionArgument>(value))
|
|
return arg->getDecl()->getLoc();
|
|
llvm_unreachable("Do not know how to get source loc for value?!");
|
|
}
|
|
|
|
void ConsumeOperatorCopyableValuesChecker::emitDiagnosticForMove(
|
|
SILValue borrowedValue, StringRef borrowedValueName, MoveValueInst *mvi) {
|
|
auto &astContext = fn->getASTContext();
|
|
|
|
// First we emit the main error and then the note on where the move was.
|
|
diagnose(astContext, getSourceLocFromValue(borrowedValue),
|
|
diag::sil_movechecking_value_used_after_consume,
|
|
borrowedValueName);
|
|
if (auto sourceLoc = mvi->getLoc().getSourceLoc()) {
|
|
diagnose(astContext, sourceLoc,
|
|
diag::sil_movechecking_consuming_use_here);
|
|
}
|
|
|
|
// Then we do a bit of work to figure out where /all/ of the later uses than
|
|
// mvi are so we can emit notes to the user telling them this is a problem
|
|
// use. We can do a little more work here since we already know that we are
|
|
// going to be emitting a diagnostic and thus later parts of the compiler are
|
|
// not going to run. First we look for uses in the same block as our move.
|
|
auto *mviBlock = mvi->getParent();
|
|
auto mviBlockLiveness = livenessInfo.liveness->getBlockLiveness(mviBlock);
|
|
switch (mviBlockLiveness) {
|
|
case PrunedLiveBlocks::Dead:
|
|
llvm_unreachable("We should never see this");
|
|
case PrunedLiveBlocks::LiveWithin: {
|
|
// The boundary was within our block. We need to search for uses later than
|
|
// us and emit a diagnostic upon them and then return. We leave the rest of
|
|
// the function for the implementation of the LiveOutCase.
|
|
//
|
|
// NOTE: This does mean that once the user fixes this use, they will get
|
|
// additional errors that we did not diagnose before. We do this to simplify
|
|
// the implementation noting that the program in either case will not
|
|
// compile meaning correctness will be maintained despite this
|
|
// implementation choice.
|
|
for (SILInstruction &inst :
|
|
make_range(std::next(mvi->getIterator()), mviBlock->end())) {
|
|
switch (livenessInfo.liveness->isInterestingUser(&inst)) {
|
|
case PrunedLiveness::NonUser:
|
|
break;
|
|
case PrunedLiveness::NonLifetimeEndingUse:
|
|
case PrunedLiveness::LifetimeEndingUse:
|
|
LLVM_DEBUG(llvm::dbgs() << "Emitting note for in block use: " << inst);
|
|
if (auto sourceLoc = inst.getLoc().getSourceLoc()) {
|
|
diagnose(astContext, sourceLoc,
|
|
diag::sil_movechecking_nonconsuming_use_here);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
case PrunedLiveBlocks::LiveOut: {
|
|
// The boundary was later than us, we need to do a full on CFG search, which
|
|
// we do below.
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Just to check for dumb mistakes, assert we are LiveOut here.
|
|
assert(mviBlockLiveness == PrunedLiveBlocks::LiveOut &&
|
|
"We are handling only the live out case here. The rest of the cases "
|
|
"were handled in the switch above and return early upon success");
|
|
|
|
// Ok, our boundary was later, so we need to search the CFG along successor
|
|
// edges starting at the successors's of our move function block
|
|
BasicBlockWorklist worklist(mvi->getFunction());
|
|
for (auto *succBlock : mvi->getParent()->getSuccessorBlocks()) {
|
|
worklist.pushIfNotVisited(succBlock);
|
|
}
|
|
|
|
// In order to make sure that we do not miss uses that are within loops, we
|
|
// maintain a list of all user sets.
|
|
//
|
|
// DISCUSSION: The issue is that a block at a deeper loop level than our def,
|
|
// even if it contained the use that triggered the issue will be LiveOut. So
|
|
// when we see a live out block, we perform this extra check and emit a
|
|
// diagnostic if needed.
|
|
BasicBlockSet usesToCheckForInLiveOutBlocks(mvi->getFunction());
|
|
for (auto *user : livenessInfo.nonLifetimeEndingUsesInLiveOut)
|
|
usesToCheckForInLiveOutBlocks.insert(user->getParent());
|
|
for (auto *consumingUse : livenessInfo.consumingUse) {
|
|
// We ignore consuming uses that are destroy_value since in our model they
|
|
// do not provide liveness.
|
|
if (!isa<DestroyValueInst>(consumingUse->getUser()))
|
|
usesToCheckForInLiveOutBlocks.insert(consumingUse->getParentBlock());
|
|
}
|
|
|
|
while (auto *block = worklist.pop()) {
|
|
// First do a quick check if we are not a live out block. If so, the
|
|
// boundary was within the block. We need to search for interesting uses in
|
|
// the block and then emit diagnostics upon them. We then continue without
|
|
// adding successors since we do not need to look further than the pruned
|
|
// liveness boundary for uses.
|
|
if (PrunedLiveBlocks::LiveOut !=
|
|
livenessInfo.liveness->getBlockLiveness(block)) {
|
|
for (SILInstruction &inst : *block) {
|
|
switch (livenessInfo.liveness->isInterestingUser(&inst)) {
|
|
case PrunedLiveness::NonUser:
|
|
break;
|
|
case PrunedLiveness::NonLifetimeEndingUse:
|
|
case PrunedLiveness::LifetimeEndingUse:
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "(3) Emitting diagnostic for user: " << inst);
|
|
if (auto sourceLoc = inst.getLoc().getSourceLoc()) {
|
|
diagnose(astContext, sourceLoc,
|
|
diag::sil_movechecking_nonconsuming_use_here);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Otherwise, we have a live out block. First before we do anything, add the
|
|
// successors of this block to the worklist.
|
|
for (auto *succBlock : block->getSuccessorBlocks())
|
|
worklist.pushIfNotVisited(succBlock);
|
|
|
|
// Then check if we have any of those deeper loop nest uses. If not, we are
|
|
// done with this block and continue...
|
|
if (!usesToCheckForInLiveOutBlocks.contains(block))
|
|
continue;
|
|
|
|
// Ok! This is a live out block with a use we need to emit an error for . We
|
|
// know it is reachable from the move since we are walking successors from
|
|
// the move block. Of course, if we do not have any such uses... just
|
|
// continue.
|
|
for (SILInstruction &inst : *block) {
|
|
if (livenessInfo.nonLifetimeEndingUsesInLiveOut.contains(&inst)) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "(1) Emitting diagnostic for user: " << inst);
|
|
if (auto sourceLoc = inst.getLoc().getSourceLoc()) {
|
|
diagnose(astContext, sourceLoc,
|
|
diag::sil_movechecking_nonconsuming_use_here);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
for (auto &op : inst.getAllOperands()) {
|
|
if (livenessInfo.consumingUse.contains(&op)) {
|
|
// If one of our in loop moves is ourselves, then we know that our
|
|
// original value is outside of the loop and thus we have a loop
|
|
// carry dataflow violation.
|
|
if (mvi == &inst) {
|
|
diagnose(astContext, inst.getLoc().getSourceLoc(),
|
|
diag::sil_movechecking_consumed_in_loop_here);
|
|
continue;
|
|
}
|
|
// We ignore consuming uses that are destroy_value since in our model
|
|
// they do not provide liveness.
|
|
if (isa<DestroyValueInst>(inst))
|
|
continue;
|
|
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "(2) Emitting diagnostic for user: " << inst);
|
|
if (auto sourceLoc = inst.getLoc().getSourceLoc()) {
|
|
diagnose(astContext, sourceLoc,
|
|
diag::sil_movechecking_nonconsuming_use_here);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ConsumeOperatorCopyableValuesChecker::check() {
|
|
llvm::SmallSetVector<SILValue, 32> valuesToCheck;
|
|
|
|
for (auto *arg : fn->getEntryBlock()->getSILFunctionArguments()) {
|
|
auto ownership = arg->getOwnershipKind();
|
|
if ((ownership == OwnershipKind::Owned ||
|
|
ownership == OwnershipKind::Guaranteed) &&
|
|
!arg->getType().isMoveOnly()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Found owned arg to check: " << *arg);
|
|
valuesToCheck.insert(arg);
|
|
}
|
|
}
|
|
|
|
for (auto &block : *fn) {
|
|
for (auto &ii : block) {
|
|
if (auto *mvi = dyn_cast<MoveValueInst>(&ii)) {
|
|
if (mvi->isFromVarDecl()
|
|
&& mvi->getOwnershipKind() != OwnershipKind::None
|
|
&& !mvi->getType().isMoveOnly()) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "Found lexical lifetime to check: " << *mvi);
|
|
valuesToCheck.insert(mvi);
|
|
}
|
|
}
|
|
if (auto *bbi = dyn_cast<BeginBorrowInst>(&ii)) {
|
|
if (bbi->isFromVarDecl() && !bbi->getType().isMoveOnly()) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "Found lexical lifetime to check: " << *bbi);
|
|
valuesToCheck.insert(bbi);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (valuesToCheck.empty()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "No values to check! Exiting early!\n");
|
|
return false;
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "Found at least one value to check, performing checking.\n");
|
|
auto valuesToProcess =
|
|
llvm::ArrayRef(valuesToCheck.begin(), valuesToCheck.end());
|
|
|
|
// If we do not emit any diagnostics, we need to put in a break after each dbg
|
|
// info carrying inst for a lexical value that we find a move on. This ensures
|
|
// that we avoid a behavior today in SelectionDAG that causes dbg info addr to
|
|
// be always sunk to the end of a block.
|
|
//
|
|
// TODO: We should add llvm.dbg.addr support for fastisel and also teach
|
|
// CodeGen how to handle llvm.dbg.addr better.
|
|
while (!valuesToProcess.empty()) {
|
|
BitfieldRef<DiagnosticPrunedLiveness>::StackState livenessBitfieldContainer(
|
|
livenessInfo.liveness, fn, nullptr,
|
|
&livenessInfo.nonLifetimeEndingUsesInLiveOut);
|
|
|
|
auto lexicalValue = valuesToProcess.front();
|
|
valuesToProcess = valuesToProcess.drop_front(1);
|
|
LLVM_DEBUG(llvm::dbgs() << "Visiting: " << *lexicalValue);
|
|
|
|
// Then compute liveness.
|
|
SWIFT_DEFER { livenessInfo.clear(); };
|
|
livenessInfo.initDef(lexicalValue);
|
|
|
|
// We only fail to optimize if for some reason we hit reborrows. This is
|
|
// temporary since we really should just ban reborrows in Raw SIL.
|
|
bool canOptimize = livenessInfo.compute();
|
|
if (!canOptimize)
|
|
continue;
|
|
|
|
// Then look at all of our found consuming uses. See if any of these are
|
|
// _move that are within the boundary.
|
|
bool foundMove = false;
|
|
SmallVector<SILInstruction *, 2> validMoves;
|
|
auto dbgVarInst = DebugVarCarryingInst::getFromValue(lexicalValue);
|
|
StringRef varName = DebugVarCarryingInst::getName(dbgVarInst);
|
|
for (auto *use : livenessInfo.consumingUse) {
|
|
if (auto *mvi = dyn_cast<MoveValueInst>(use->getUser())) {
|
|
// Only emit diagnostics if our move value allows us to.
|
|
if (!mvi->getAllowDiagnostics())
|
|
continue;
|
|
|
|
// Now that we know we may emit diagnostics for this, unset allows
|
|
// diagnostics so that we skip these when we search at the end for
|
|
// unvisited move_value [allows_diagnostics].
|
|
mvi->setAllowsDiagnostics(false);
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Move Value: " << *mvi);
|
|
if (livenessInfo.liveness->isWithinBoundary(
|
|
mvi, /*deadEndBlocks=*/nullptr)) {
|
|
LLVM_DEBUG(llvm::dbgs() << " WithinBoundary: Yes!\n");
|
|
emitDiagnosticForMove(lexicalValue, varName, mvi);
|
|
} else {
|
|
LLVM_DEBUG(llvm::dbgs() << " WithinBoundary: No!\n");
|
|
if (auto varInfo = dbgVarInst.getVarInfo()) {
|
|
auto *next = mvi->getNextInstruction();
|
|
SILBuilderWithScope builder(next);
|
|
// We need to make sure any undefs we put in are the same loc/debug
|
|
// scope as our original so that the backend treats them as
|
|
// referring to the same "debug entity".
|
|
builder.setCurrentDebugScope(dbgVarInst->getDebugScope());
|
|
builder.createDebugValue(
|
|
dbgVarInst->getLoc(), SILUndef::get(mvi->getOperand()),
|
|
*varInfo, DontPoisonRefs, UsesMoveableValueDebugInfo);
|
|
}
|
|
validMoves.push_back(mvi);
|
|
}
|
|
foundMove = true;
|
|
}
|
|
}
|
|
|
|
// If we found a move, mark our debug var inst as having a moved value. This
|
|
// ensures we emit llvm.dbg.addr instead of llvm.dbg.declare in IRGen.
|
|
if (foundMove) {
|
|
dbgVarInst.markAsMoved();
|
|
}
|
|
|
|
if (validMoves.size() > 0) {
|
|
canonicalizer.clear();
|
|
canonicalizer.canonicalizeValueLifetime(lexicalValue, validMoves);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Unsupported Use Case Errors
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static void emitUnsupportedUseCaseError(MoveValueInst *mvi) {
|
|
auto &astContext = mvi->getModule().getASTContext();
|
|
auto diag = diag::sil_movekillscopyablevalue_move_applied_to_unsupported_move;
|
|
diagnose(astContext, mvi->getLoc().getSourceLoc(), diag);
|
|
mvi->setAllowsDiagnostics(false);
|
|
}
|
|
|
|
/// Try to pattern match if we were trying to move a global. In such a case,
|
|
/// emit a better error.
|
|
static bool tryEmitCannotConsumeNonLocalMemoryError(MoveValueInst *mvi) {
|
|
auto *li = dyn_cast<LoadInst>(mvi->getOperand());
|
|
if (!li)
|
|
return false;
|
|
|
|
auto &astContext = mvi->getModule().getASTContext();
|
|
if (isa<GlobalAddrInst>(stripAccessMarkers(li->getOperand()))) {
|
|
auto diag = diag::sil_movekillscopyable_move_applied_to_nonlocal_memory;
|
|
diagnose(astContext, mvi->getLoc().getSourceLoc(), diag, 0);
|
|
mvi->setAllowsDiagnostics(false);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Top Level Entrypoint
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
class ConsumeOperatorCopyableValuesCheckerPass : public SILFunctionTransform {
|
|
void run() override {
|
|
auto *fn = getFunction();
|
|
|
|
// Don't rerun diagnostics on deserialized functions.
|
|
if (fn->wasDeserializedCanonical())
|
|
return;
|
|
|
|
assert(fn->getModule().getStage() == SILStage::Raw &&
|
|
"Should only run on Raw SIL");
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "*** Checking moved values in fn: "
|
|
<< getFunction()->getName() << '\n');
|
|
|
|
auto *dominanceAnalysis = getAnalysis<DominanceAnalysis>();
|
|
auto *dominance = dominanceAnalysis->get(fn);
|
|
auto *calleeAnalysis = getAnalysis<BasicCalleeAnalysis>();
|
|
auto *deadEndBlocksAnalysis = getAnalysis<DeadEndBlocksAnalysis>();
|
|
ConsumeOperatorCopyableValuesChecker checker(
|
|
getFunction(), dominance, calleeAnalysis, deadEndBlocksAnalysis);
|
|
auto *loopAnalysis = getAnalysis<SILLoopAnalysis>();
|
|
|
|
if (checker.check()) {
|
|
// If we already had dominance or loop info generated, update them when
|
|
// splitting blocks.
|
|
AnalysisPreserver preserveDominance(dominanceAnalysis);
|
|
AnalysisPreserver preserveLoop(loopAnalysis);
|
|
invalidateAnalysis(
|
|
SILAnalysis::InvalidationKind::BranchesAndInstructions);
|
|
}
|
|
|
|
// Now search through our function one last time and:
|
|
//
|
|
// 1. Given any move_value on a move only type, just unset the allows
|
|
// diagnostics flag. The move checker will have emitted any errors caused
|
|
// by our move [allows_diagnostic] earlier in the compilation pipeline.
|
|
//
|
|
// 2. Any move_value [allows_diagnostics] that remain that are not on a move
|
|
// only type are ones that we did not know how to check so emit a
|
|
// diagnostic so the user doesn't assume that they have guarantees.
|
|
//
|
|
// TODO: Emit specific diagnostics here (e.x.: _move of global).
|
|
for (auto &block : *fn) {
|
|
for (auto &inst : block) {
|
|
if (auto *mvi = dyn_cast<MoveValueInst>(&inst)) {
|
|
if (mvi->getAllowDiagnostics()) {
|
|
if (mvi->getType().isMoveOnly()) {
|
|
mvi->setAllowsDiagnostics(false);
|
|
continue;
|
|
}
|
|
|
|
// Try to emit a better error if we try to consume a global.
|
|
if (tryEmitCannotConsumeNonLocalMemoryError(mvi))
|
|
continue;
|
|
|
|
if (!DisableUnhandledMoveDiagnostic)
|
|
emitUnsupportedUseCaseError(mvi);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
SILTransform *swift::createConsumeOperatorCopyableValuesChecker() {
|
|
return new ConsumeOperatorCopyableValuesCheckerPass();
|
|
}
|