mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
This is just moving up the declaration in the chain of dependencies so that I can write logic in PartitionUtils.h using it. I also added entrypoints to lookup the ReprensetativeValue for our various emitters.
2130 lines
78 KiB
C++
2130 lines
78 KiB
C++
//===--- TransferNonSendable.cpp ------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2023 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 "transfer-non-sendable"
|
|
|
|
#include "swift/AST/ASTWalker.h"
|
|
#include "swift/AST/DiagnosticsSIL.h"
|
|
#include "swift/AST/Expr.h"
|
|
#include "swift/AST/ProtocolConformance.h"
|
|
#include "swift/AST/SourceFile.h"
|
|
#include "swift/AST/Type.h"
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/Basic/FrozenMultiMap.h"
|
|
#include "swift/Basic/ImmutablePointerSet.h"
|
|
#include "swift/SIL/BasicBlockData.h"
|
|
#include "swift/SIL/BasicBlockDatastructures.h"
|
|
#include "swift/SIL/DynamicCasts.h"
|
|
#include "swift/SIL/MemAccessUtils.h"
|
|
#include "swift/SIL/NodeDatastructures.h"
|
|
#include "swift/SIL/OperandDatastructures.h"
|
|
#include "swift/SIL/OwnershipUtils.h"
|
|
#include "swift/SIL/PatternMatch.h"
|
|
#include "swift/SIL/SILBasicBlock.h"
|
|
#include "swift/SIL/SILBuilder.h"
|
|
#include "swift/SIL/SILFunction.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/Test.h"
|
|
#include "swift/SILOptimizer/Analysis/RegionAnalysis.h"
|
|
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
|
#include "swift/SILOptimizer/Utils/PartitionUtils.h"
|
|
#include "swift/SILOptimizer/Utils/VariableNameUtils.h"
|
|
#include "swift/Sema/Concurrency.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
using namespace swift;
|
|
using namespace swift::PartitionPrimitives;
|
|
using namespace swift::PatternMatch;
|
|
using namespace swift::regionanalysisimpl;
|
|
|
|
namespace {
|
|
|
|
using TransferringOperandSetFactory = Partition::TransferringOperandSetFactory;
|
|
using Element = PartitionPrimitives::Element;
|
|
using Region = PartitionPrimitives::Region;
|
|
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Utilities
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static std::optional<DiagnosticBehavior>
|
|
getDiagnosticBehaviorLimitForValue(SILValue value) {
|
|
auto *nom = value->getType().getNominalOrBoundGenericNominal();
|
|
if (!nom)
|
|
return {};
|
|
|
|
auto declRef = value->getFunction()->getDeclRef();
|
|
if (!declRef)
|
|
return {};
|
|
|
|
auto *fromDC = declRef.getInnermostDeclContext();
|
|
return getConcurrencyDiagnosticBehaviorLimit(nom, fromDC);
|
|
}
|
|
|
|
static std::optional<SILDeclRef> getDeclRefForCallee(SILInstruction *inst) {
|
|
auto fas = FullApplySite::isa(inst);
|
|
if (!fas)
|
|
return {};
|
|
|
|
SILValue calleeOrigin = fas.getCalleeOrigin();
|
|
|
|
while (true) {
|
|
// Intentionally don't lookup through dynamic_function_ref and
|
|
// previous_dynamic_function_ref as the target of those functions is not
|
|
// statically known.
|
|
if (auto *fri = dyn_cast<FunctionRefInst>(calleeOrigin)) {
|
|
if (auto *callee = fri->getReferencedFunctionOrNull()) {
|
|
if (auto declRef = callee->getDeclRef())
|
|
return declRef;
|
|
}
|
|
}
|
|
|
|
if (auto *mi = dyn_cast<MethodInst>(calleeOrigin)) {
|
|
return mi->getMember();
|
|
}
|
|
|
|
if (auto *pai = dyn_cast<PartialApplyInst>(calleeOrigin)) {
|
|
calleeOrigin = pai->getCalleeOrigin();
|
|
continue;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
}
|
|
|
|
static std::optional<std::pair<DescriptiveDeclKind, DeclName>>
|
|
getTransferringApplyCalleeInfo(SILInstruction *inst) {
|
|
auto declRef = getDeclRefForCallee(inst);
|
|
if (!declRef)
|
|
return {};
|
|
|
|
auto *decl = declRef->getDecl();
|
|
if (!decl || !decl->hasName())
|
|
return {};
|
|
|
|
return {{decl->getDescriptiveKind(), decl->getName()}};
|
|
}
|
|
|
|
static Expr *inferArgumentExprFromApplyExpr(ApplyExpr *sourceApply,
|
|
FullApplySite fai,
|
|
const Operand *op) {
|
|
|
|
Expr *foundExpr = nullptr;
|
|
|
|
// If we have self, then infer it.
|
|
if (fai.hasSelfArgument() && op == &fai.getSelfArgumentOperand()) {
|
|
if (auto callExpr = dyn_cast<CallExpr>(sourceApply))
|
|
if (auto calledExpr =
|
|
dyn_cast<DotSyntaxCallExpr>(callExpr->getDirectCallee()))
|
|
foundExpr = calledExpr->getBase();
|
|
} else {
|
|
// Otherwise, try to infer using the operand of the ApplyExpr.
|
|
unsigned argNum = [&]() -> unsigned {
|
|
if (fai.isCalleeOperand(*op))
|
|
return op->getOperandNumber();
|
|
return fai.getAppliedArgIndexWithoutIndirectResults(*op);
|
|
}();
|
|
|
|
// Something happened that we do not understand.
|
|
if (argNum >= sourceApply->getArgs()->size()) {
|
|
return nullptr;
|
|
}
|
|
|
|
foundExpr = sourceApply->getArgs()->getExpr(argNum);
|
|
|
|
// If we have an erasure expression, lets use the original type. We do
|
|
// this since we are not saying the specific parameter that is the
|
|
// issue and we are using the type to explain it to the user.
|
|
if (auto *erasureExpr = dyn_cast<ErasureExpr>(foundExpr))
|
|
foundExpr = erasureExpr->getSubExpr();
|
|
}
|
|
|
|
return foundExpr;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Diagnostics
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseError(ASTContext &context, SourceLoc loc,
|
|
Diag<T...> diag, U &&...args) {
|
|
return std::move(context.Diags.diagnose(loc, diag, std::forward<U>(args)...)
|
|
.warnUntilSwiftVersion(6));
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseError(ASTContext &context, SILLocation loc,
|
|
Diag<T...> diag, U &&...args) {
|
|
return ::diagnoseError(context, loc.getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseError(const PartitionOp &op, Diag<T...> diag,
|
|
U &&...args) {
|
|
return ::diagnoseError(op.getSourceInst()->getFunction()->getASTContext(),
|
|
op.getSourceLoc().getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseError(const Operand *op, Diag<T...> diag,
|
|
U &&...args) {
|
|
return ::diagnoseError(op->getUser()->getFunction()->getASTContext(),
|
|
op->getUser()->getLoc().getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseError(const SILInstruction *inst,
|
|
Diag<T...> diag, U &&...args) {
|
|
return ::diagnoseError(inst->getFunction()->getASTContext(),
|
|
inst->getLoc().getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseNote(ASTContext &context, SourceLoc loc,
|
|
Diag<T...> diag, U &&...args) {
|
|
return context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseNote(ASTContext &context, SILLocation loc,
|
|
Diag<T...> diag, U &&...args) {
|
|
return ::diagnoseNote(context, loc.getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseNote(const PartitionOp &op, Diag<T...> diag,
|
|
U &&...args) {
|
|
return ::diagnoseNote(op.getSourceInst()->getFunction()->getASTContext(),
|
|
op.getSourceLoc().getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseNote(const Operand *op, Diag<T...> diag,
|
|
U &&...args) {
|
|
return ::diagnoseNote(op->getUser()->getFunction()->getASTContext(),
|
|
op->getUser()->getLoc().getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseNote(const SILInstruction *inst,
|
|
Diag<T...> diag, U &&...args) {
|
|
return ::diagnoseNote(inst->getFunction()->getASTContext(),
|
|
inst->getLoc().getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Require Liveness
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
class BlockLivenessInfo {
|
|
// Generation counter so we do not need to reallocate.
|
|
unsigned generation = 0;
|
|
SILInstruction *firstRequireInst = nullptr;
|
|
|
|
void resetIfNew(unsigned newGeneration) {
|
|
if (generation == newGeneration)
|
|
return;
|
|
generation = newGeneration;
|
|
firstRequireInst = nullptr;
|
|
}
|
|
|
|
public:
|
|
SILInstruction *getInst(unsigned callerGeneration) {
|
|
resetIfNew(callerGeneration);
|
|
return firstRequireInst;
|
|
}
|
|
|
|
void setInst(unsigned callerGeneration, SILInstruction *newValue) {
|
|
resetIfNew(callerGeneration);
|
|
firstRequireInst = newValue;
|
|
}
|
|
};
|
|
|
|
/// We only want to emit errors for the first requires along a path from a
|
|
/// transfer instruction. We discover this by walking from user blocks to
|
|
struct RequireLiveness {
|
|
unsigned generation;
|
|
SILInstruction *transferInst;
|
|
BasicBlockData<BlockLivenessInfo> &blockLivenessInfo;
|
|
InstructionSet allRequires;
|
|
InstructionSetWithSize finalRequires;
|
|
|
|
/// If we have requires in the def block before our transfer, this is the
|
|
/// first require.
|
|
SILInstruction *firstRequireBeforeTransferInDefBlock = nullptr;
|
|
|
|
RequireLiveness(unsigned generation, Operand *transferOp,
|
|
BasicBlockData<BlockLivenessInfo> &blockLivenessInfo)
|
|
: generation(generation), transferInst(transferOp->getUser()),
|
|
blockLivenessInfo(blockLivenessInfo),
|
|
allRequires(transferOp->getParentFunction()),
|
|
finalRequires(transferOp->getParentFunction()) {}
|
|
|
|
template <typename Collection>
|
|
void process(Collection collection);
|
|
|
|
/// Attempt to process requireInst for our def block. Returns false if
|
|
/// requireInst was before our def and we need to do interprocedural
|
|
/// processing. Returns true if requireInst was after our transferInst and we
|
|
/// were able to appropriately determine if we should emit it or not.
|
|
void processDefBlock();
|
|
|
|
/// Process all requires in block, updating blockLivenessInfo.
|
|
void processNonDefBlock(SILBasicBlock *block);
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void RequireLiveness::processDefBlock() {
|
|
LLVM_DEBUG(llvm::dbgs() << " Processing def block!\n");
|
|
// First walk from the beginning of the block to the transfer instruction to
|
|
// see if we have any requires before our def. Once we find one, we can skip
|
|
// the traversal and jump straight to the transfer.
|
|
for (auto ii = transferInst->getParent()->begin(),
|
|
ie = transferInst->getIterator();
|
|
ii != ie; ++ii) {
|
|
if (allRequires.contains(&*ii) && !firstRequireBeforeTransferInDefBlock) {
|
|
firstRequireBeforeTransferInDefBlock = &*ii;
|
|
LLVM_DEBUG(llvm::dbgs() << " Found transfer before def: "
|
|
<< *firstRequireBeforeTransferInDefBlock);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Then walk from our transferInst to the end of the block looking for the
|
|
// first require inst. Once we find it... return.
|
|
//
|
|
// NOTE: We start walking at the transferInst since the transferInst could use
|
|
// the requireInst as well.
|
|
for (auto ii = transferInst->getIterator(),
|
|
ie = transferInst->getParent()->end();
|
|
ii != ie; ++ii) {
|
|
if (!allRequires.contains(&*ii))
|
|
continue;
|
|
|
|
finalRequires.insert(&*ii);
|
|
LLVM_DEBUG(llvm::dbgs() << " Found transfer after def: " << *ii);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void RequireLiveness::processNonDefBlock(SILBasicBlock *block) {
|
|
// Walk from the bottom to the top... assigning to our block state.
|
|
auto blockState = blockLivenessInfo.get(block);
|
|
for (auto &inst : llvm::make_range(block->rbegin(), block->rend())) {
|
|
if (!finalRequires.contains(&inst))
|
|
continue;
|
|
blockState.get()->setInst(generation, &inst);
|
|
}
|
|
}
|
|
|
|
template <typename Collection>
|
|
void RequireLiveness::process(Collection requireInstList) {
|
|
LLVM_DEBUG(llvm::dbgs() << "==> Performing Require Liveness for: "
|
|
<< *transferInst);
|
|
|
|
// Then put all of our requires into our allRequires set.
|
|
BasicBlockWorklist initializingWorklist(transferInst->getFunction());
|
|
for (auto require : requireInstList) {
|
|
LLVM_DEBUG(llvm::dbgs() << " Require Inst: " << **require);
|
|
allRequires.insert(*require);
|
|
initializingWorklist.pushIfNotVisited(require->getParent());
|
|
}
|
|
|
|
// Then process our def block to see if we have any requires before and after
|
|
// the transferInst...
|
|
processDefBlock();
|
|
|
|
// If we found /any/ requries after the transferInst, we can bail early since
|
|
// that is guaranteed to dominate all further requires.
|
|
if (!finalRequires.empty()) {
|
|
LLVM_DEBUG(
|
|
llvm::dbgs()
|
|
<< " Found transfer after def in def block! Exiting early!\n");
|
|
return;
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << " Did not find transfer after def in def "
|
|
"block! Walking blocks!\n");
|
|
|
|
// If we found a transfer in the def block before our def, add it to the block
|
|
// state for the def.
|
|
if (firstRequireBeforeTransferInDefBlock) {
|
|
LLVM_DEBUG(
|
|
llvm::dbgs()
|
|
<< " Found a require before transfer! Adding to block state!\n");
|
|
auto blockState = blockLivenessInfo.get(transferInst->getParent());
|
|
blockState.get()->setInst(generation, firstRequireBeforeTransferInDefBlock);
|
|
}
|
|
|
|
// Then for each require block that isn't a def block transfer, find the
|
|
// earliest transfer inst.
|
|
while (auto *requireBlock = initializingWorklist.pop()) {
|
|
auto blockState = blockLivenessInfo.get(requireBlock);
|
|
for (auto &inst : *requireBlock) {
|
|
if (!allRequires.contains(&inst))
|
|
continue;
|
|
LLVM_DEBUG(llvm::dbgs() << " Mapping Block bb"
|
|
<< requireBlock->getDebugID() << " to: " << inst);
|
|
blockState.get()->setInst(generation, &inst);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Then walk from our def block looking for setInst blocks.
|
|
auto *transferBlock = transferInst->getParent();
|
|
BasicBlockWorklist worklist(transferInst->getFunction());
|
|
for (auto *succBlock : transferBlock->getSuccessorBlocks())
|
|
worklist.pushIfNotVisited(succBlock);
|
|
|
|
while (auto *next = worklist.pop()) {
|
|
// Check if we found an earliest requires... if so, add that to final
|
|
// requires and continue. We don't want to visit successors.
|
|
auto blockState = blockLivenessInfo.get(next);
|
|
if (auto *inst = blockState.get()->getInst(generation)) {
|
|
finalRequires.insert(inst);
|
|
continue;
|
|
}
|
|
|
|
// Do not look at successors of the transfer block.
|
|
if (next == transferBlock)
|
|
continue;
|
|
|
|
// Otherwise, we did not find a requires and need to search further
|
|
// successors.
|
|
for (auto *succBlock : next->getSuccessorBlocks())
|
|
worklist.pushIfNotVisited(succBlock);
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Forward Declaration Of TransferNonSendableImpl
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
struct InOutSendingNotDisconnectedInfo {
|
|
/// The function exiting inst where the 'inout sending' parameter was actor
|
|
/// isolated.
|
|
TermInst *functionExitingInst;
|
|
|
|
/// The 'inout sending' param that we are emitting an error for.
|
|
SILValue inoutSendingParam;
|
|
|
|
/// The dynamic actor isolated region info of our 'inout sending' value's
|
|
/// region at the terminator inst.
|
|
SILDynamicMergedIsolationInfo actorIsolatedRegionInfo;
|
|
|
|
InOutSendingNotDisconnectedInfo(
|
|
SILInstruction *functionExitingInst, SILValue inoutSendingParam,
|
|
SILDynamicMergedIsolationInfo actorIsolatedRegionInfo)
|
|
: functionExitingInst(cast<TermInst>(functionExitingInst)),
|
|
inoutSendingParam(inoutSendingParam),
|
|
actorIsolatedRegionInfo(actorIsolatedRegionInfo) {}
|
|
};
|
|
|
|
struct TransferredNonTransferrableInfo {
|
|
/// The use that actually caused the transfer.
|
|
Operand *transferredOperand;
|
|
|
|
/// The non-transferrable value that is in the same region as \p
|
|
/// transferredOperand.get().
|
|
llvm::PointerUnion<SILValue, SILInstruction *> nonTransferrable;
|
|
|
|
/// The region info that describes the dynamic dataflow derived isolation
|
|
/// region info for the non-transferrable value.
|
|
///
|
|
/// This is equal to the merge of the IsolationRegionInfo from all elements in
|
|
/// nonTransferrable's region when the error was diagnosed.
|
|
SILDynamicMergedIsolationInfo isolationRegionInfo;
|
|
|
|
TransferredNonTransferrableInfo(
|
|
Operand *transferredOperand, SILValue nonTransferrableValue,
|
|
SILDynamicMergedIsolationInfo isolationRegionInfo)
|
|
: transferredOperand(transferredOperand),
|
|
nonTransferrable(nonTransferrableValue),
|
|
isolationRegionInfo(isolationRegionInfo) {}
|
|
TransferredNonTransferrableInfo(
|
|
Operand *transferredOperand, SILInstruction *nonTransferrableInst,
|
|
SILDynamicMergedIsolationInfo isolationRegionInfo)
|
|
: transferredOperand(transferredOperand),
|
|
nonTransferrable(nonTransferrableInst),
|
|
isolationRegionInfo(isolationRegionInfo) {}
|
|
};
|
|
|
|
/// Wrapper around a SILInstruction that internally specifies whether we are
|
|
/// dealing with an inout reinitialization needed or if it is just a normal
|
|
/// use after transfer.
|
|
class RequireInst {
|
|
public:
|
|
enum Kind {
|
|
UseAfterTransfer,
|
|
InOutReinitializationNeeded,
|
|
};
|
|
|
|
private:
|
|
llvm::PointerIntPair<SILInstruction *, 1> instAndKind;
|
|
|
|
RequireInst(SILInstruction *inst, Kind kind) : instAndKind(inst, kind) {}
|
|
|
|
public:
|
|
static RequireInst forUseAfterTransfer(SILInstruction *inst) {
|
|
return {inst, Kind::UseAfterTransfer};
|
|
}
|
|
|
|
static RequireInst forInOutReinitializationNeeded(SILInstruction *inst) {
|
|
return {inst, Kind::InOutReinitializationNeeded};
|
|
}
|
|
|
|
SILInstruction *getInst() const { return instAndKind.getPointer(); }
|
|
Kind getKind() const { return Kind(instAndKind.getInt()); }
|
|
|
|
SILInstruction *operator*() const { return getInst(); }
|
|
SILInstruction *operator->() const { return getInst(); }
|
|
};
|
|
|
|
class TransferNonSendableImpl {
|
|
RegionAnalysisFunctionInfo *regionInfo;
|
|
SmallFrozenMultiMap<Operand *, RequireInst, 8>
|
|
transferOpToRequireInstMultiMap;
|
|
SmallVector<TransferredNonTransferrableInfo, 8>
|
|
transferredNonTransferrableInfoList;
|
|
SmallVector<InOutSendingNotDisconnectedInfo, 8>
|
|
inoutSendingNotDisconnectedInfoList;
|
|
|
|
public:
|
|
TransferNonSendableImpl(RegionAnalysisFunctionInfo *regionInfo)
|
|
: regionInfo(regionInfo) {}
|
|
|
|
void emitDiagnostics();
|
|
|
|
private:
|
|
void runDiagnosticEvaluator();
|
|
|
|
void emitUseAfterTransferDiagnostics();
|
|
void emitTransferredNonTransferrableDiagnostics();
|
|
void emitInOutSendingNotDisconnectedInfoList();
|
|
};
|
|
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: UseAfterTransfer Diagnostic Inference
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
class UseAfterTransferDiagnosticEmitter {
|
|
Operand *transferOp;
|
|
SmallVectorImpl<RequireInst> &requireInsts;
|
|
bool emittedErrorDiagnostic = false;
|
|
|
|
public:
|
|
UseAfterTransferDiagnosticEmitter(Operand *transferOp,
|
|
SmallVectorImpl<RequireInst> &requireInsts)
|
|
: transferOp(transferOp), requireInsts(requireInsts) {}
|
|
|
|
~UseAfterTransferDiagnosticEmitter() {
|
|
// If we were supposed to emit a diagnostic and didn't emit an unknown
|
|
// pattern error.
|
|
if (!emittedErrorDiagnostic)
|
|
emitUnknownPatternError();
|
|
}
|
|
|
|
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
|
|
return getDiagnosticBehaviorLimitForValue(transferOp->get());
|
|
}
|
|
|
|
/// If we can find a callee decl name, return that. None otherwise.
|
|
std::optional<std::pair<DescriptiveDeclKind, DeclName>>
|
|
getTransferringCalleeInfo() const {
|
|
return getTransferringApplyCalleeInfo(transferOp->getUser());
|
|
}
|
|
|
|
void
|
|
emitNamedIsolationCrossingError(SILLocation loc, Identifier name,
|
|
SILIsolationInfo namedValuesIsolationInfo,
|
|
ApplyIsolationCrossing isolationCrossing) {
|
|
// Emit the short error.
|
|
diagnoseError(loc, diag::regionbasedisolation_named_transfer_yields_race,
|
|
name)
|
|
.highlight(loc.getSourceRange())
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
|
|
// Then emit the note with greater context.
|
|
SmallString<64> descriptiveKindStr;
|
|
{
|
|
if (!namedValuesIsolationInfo.isDisconnected()) {
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
namedValuesIsolationInfo.printForDiagnostics(os);
|
|
os << ' ';
|
|
}
|
|
}
|
|
|
|
if (auto calleeInfo = getTransferringCalleeInfo()) {
|
|
diagnoseNote(
|
|
loc,
|
|
diag::regionbasedisolation_named_info_transfer_yields_race_callee,
|
|
name, descriptiveKindStr, isolationCrossing.getCalleeIsolation(),
|
|
calleeInfo->first, calleeInfo->second,
|
|
isolationCrossing.getCallerIsolation());
|
|
} else {
|
|
diagnoseNote(
|
|
loc, diag::regionbasedisolation_named_info_transfer_yields_race, name,
|
|
descriptiveKindStr, isolationCrossing.getCalleeIsolation(),
|
|
isolationCrossing.getCallerIsolation());
|
|
}
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void
|
|
emitNamedIsolationCrossingError(SILLocation loc, Identifier name,
|
|
SILIsolationInfo namedValuesIsolationInfo,
|
|
ApplyIsolationCrossing isolationCrossing,
|
|
DeclName calleeDeclName,
|
|
DescriptiveDeclKind calleeDeclKind) {
|
|
// Emit the short error.
|
|
diagnoseError(loc, diag::regionbasedisolation_named_transfer_yields_race,
|
|
name)
|
|
.highlight(loc.getSourceRange())
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
|
|
// Then emit the note with greater context.
|
|
SmallString<64> descriptiveKindStr;
|
|
{
|
|
if (!namedValuesIsolationInfo.isDisconnected()) {
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
namedValuesIsolationInfo.printForDiagnostics(os);
|
|
os << ' ';
|
|
}
|
|
}
|
|
|
|
diagnoseNote(
|
|
loc, diag::regionbasedisolation_named_info_transfer_yields_race_callee,
|
|
name, descriptiveKindStr, isolationCrossing.getCalleeIsolation(),
|
|
calleeDeclKind, calleeDeclName, isolationCrossing.getCallerIsolation());
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void emitNamedAsyncLetNoIsolationCrossingError(SILLocation loc,
|
|
Identifier name) {
|
|
// Emit the short error.
|
|
diagnoseError(loc, diag::regionbasedisolation_named_transfer_yields_race,
|
|
name)
|
|
.highlight(loc.getSourceRange())
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
|
|
diagnoseNote(
|
|
loc, diag::regionbasedisolation_named_nonisolated_asynclet_name, name);
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void emitTypedIsolationCrossing(SILLocation loc, Type inferredType,
|
|
ApplyIsolationCrossing isolationCrossing) {
|
|
diagnoseError(
|
|
loc, diag::regionbasedisolation_transfer_yields_race_with_isolation,
|
|
inferredType, isolationCrossing.getCallerIsolation(),
|
|
isolationCrossing.getCalleeIsolation())
|
|
.highlight(loc.getSourceRange())
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void emitNamedUseOfStronglyTransferredValue(SILLocation loc,
|
|
Identifier name) {
|
|
// Emit the short error.
|
|
diagnoseError(loc, diag::regionbasedisolation_named_transfer_yields_race,
|
|
name)
|
|
.highlight(loc.getSourceRange())
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
|
|
// Then emit the note with greater context.
|
|
diagnoseNote(
|
|
loc, diag::regionbasedisolation_named_value_used_after_explicit_sending,
|
|
name)
|
|
.highlight(loc.getSourceRange());
|
|
|
|
// Finally the require points.
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void emitTypedUseOfStronglyTransferredValue(SILLocation loc,
|
|
Type inferredType) {
|
|
diagnoseError(
|
|
loc,
|
|
diag::
|
|
regionbasedisolation_transfer_yields_race_stronglytransferred_binding,
|
|
inferredType)
|
|
.highlight(loc.getSourceRange())
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void emitTypedRaceWithUnknownIsolationCrossing(SILLocation loc,
|
|
Type inferredType) {
|
|
diagnoseError(loc,
|
|
diag::regionbasedisolation_transfer_yields_race_no_isolation,
|
|
inferredType)
|
|
.highlight(loc.getSourceRange())
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void emitNamedIsolationCrossingDueToCapture(
|
|
SILLocation loc, Identifier name,
|
|
SILIsolationInfo namedValuesIsolationInfo,
|
|
ApplyIsolationCrossing isolationCrossing) {
|
|
// Emit the short error.
|
|
diagnoseError(loc, diag::regionbasedisolation_named_transfer_yields_race,
|
|
name)
|
|
.highlight(loc.getSourceRange())
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
|
|
SmallString<64> descriptiveKindStr;
|
|
{
|
|
if (!namedValuesIsolationInfo.isDisconnected()) {
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
namedValuesIsolationInfo.printForDiagnostics(os);
|
|
os << ' ';
|
|
}
|
|
}
|
|
|
|
diagnoseNote(
|
|
loc, diag::regionbasedisolation_named_isolated_closure_yields_race,
|
|
descriptiveKindStr, name, isolationCrossing.getCalleeIsolation(),
|
|
isolationCrossing.getCallerIsolation())
|
|
.highlight(loc.getSourceRange());
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void emitTypedIsolationCrossingDueToCapture(
|
|
SILLocation loc, Type inferredType,
|
|
ApplyIsolationCrossing isolationCrossing) {
|
|
diagnoseError(loc, diag::regionbasedisolation_isolated_capture_yields_race,
|
|
inferredType, isolationCrossing.getCalleeIsolation(),
|
|
isolationCrossing.getCallerIsolation())
|
|
.highlight(loc.getSourceRange())
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void emitUnknownPatternError() {
|
|
if (shouldAbortOnUnknownPatternMatchError()) {
|
|
llvm::report_fatal_error(
|
|
"RegionIsolation: Aborting on unknown pattern match error");
|
|
}
|
|
|
|
diagnoseError(transferOp->getUser(),
|
|
diag::regionbasedisolation_unknown_pattern)
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
}
|
|
|
|
private:
|
|
ASTContext &getASTContext() const {
|
|
return transferOp->getFunction()->getASTContext();
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
emittedErrorDiagnostic = true;
|
|
return std::move(getASTContext()
|
|
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
|
|
.warnUntilSwiftVersion(6));
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
|
|
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
void emitRequireInstDiagnostics() {
|
|
// Now actually emit the require notes.
|
|
while (!requireInsts.empty()) {
|
|
auto require = requireInsts.pop_back_val();
|
|
switch (require.getKind()) {
|
|
case RequireInst::UseAfterTransfer:
|
|
diagnoseNote(*require, diag::regionbasedisolation_maybe_race)
|
|
.highlight(require->getLoc().getSourceRange());
|
|
continue;
|
|
case RequireInst::InOutReinitializationNeeded:
|
|
diagnoseNote(
|
|
*require,
|
|
diag::regionbasedisolation_inout_sending_must_be_reinitialized)
|
|
.highlight(require->getLoc().getSourceRange());
|
|
continue;
|
|
}
|
|
llvm_unreachable("Covered switch isn't covered?!");
|
|
}
|
|
}
|
|
};
|
|
|
|
class UseAfterTransferDiagnosticInferrer {
|
|
Operand *transferOp;
|
|
UseAfterTransferDiagnosticEmitter diagnosticEmitter;
|
|
RegionAnalysisValueMap &valueMap;
|
|
TransferringOperandToStateMap &transferringOpToStateMap;
|
|
SILLocation baseLoc = SILLocation::invalid();
|
|
Type baseInferredType;
|
|
|
|
struct AutoClosureWalker;
|
|
|
|
public:
|
|
UseAfterTransferDiagnosticInferrer(
|
|
Operand *transferOp, SmallVectorImpl<RequireInst> &requireInsts,
|
|
RegionAnalysisValueMap &valueMap,
|
|
TransferringOperandToStateMap &transferringOpToStateMap)
|
|
: transferOp(transferOp), diagnosticEmitter(transferOp, requireInsts),
|
|
valueMap(valueMap), transferringOpToStateMap(transferringOpToStateMap),
|
|
baseLoc(transferOp->getUser()->getLoc()),
|
|
baseInferredType(transferOp->get()->getType().getASTType()) {}
|
|
void infer();
|
|
|
|
Operand *getTransferringOperand() const { return transferOp; }
|
|
|
|
private:
|
|
bool initForIsolatedPartialApply(Operand *op, AbstractClosureExpr *ace);
|
|
|
|
void initForApply(Operand *op, ApplyExpr *expr);
|
|
void initForAutoclosure(Operand *op, AutoClosureExpr *expr);
|
|
|
|
Expr *getFoundExprForSelf(ApplyExpr *sourceApply) {
|
|
if (auto callExpr = dyn_cast<CallExpr>(sourceApply))
|
|
if (auto calledExpr =
|
|
dyn_cast<DotSyntaxCallExpr>(callExpr->getDirectCallee()))
|
|
return calledExpr->getBase();
|
|
return nullptr;
|
|
}
|
|
|
|
Expr *getFoundExprForParam(ApplyExpr *sourceApply, unsigned argNum) {
|
|
auto *expr = sourceApply->getArgs()->getExpr(argNum);
|
|
|
|
// If we have an erasure expression, lets use the original type. We do
|
|
// this since we are not saying the specific parameter that is the
|
|
// issue and we are using the type to explain it to the user.
|
|
if (auto *erasureExpr = dyn_cast<ErasureExpr>(expr))
|
|
expr = erasureExpr->getSubExpr();
|
|
|
|
return expr;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
bool UseAfterTransferDiagnosticInferrer::initForIsolatedPartialApply(
|
|
Operand *op, AbstractClosureExpr *ace) {
|
|
SmallVector<std::tuple<CapturedValue, unsigned, ApplyIsolationCrossing>, 8>
|
|
foundCapturedIsolationCrossing;
|
|
ace->getIsolationCrossing(foundCapturedIsolationCrossing);
|
|
if (foundCapturedIsolationCrossing.empty())
|
|
return false;
|
|
|
|
unsigned opIndex = ApplySite(op->getUser()).getAppliedArgIndex(*op);
|
|
bool emittedDiagnostic = false;
|
|
for (auto &p : foundCapturedIsolationCrossing) {
|
|
if (std::get<1>(p) != opIndex)
|
|
continue;
|
|
emittedDiagnostic = true;
|
|
|
|
auto &state = transferringOpToStateMap.get(transferOp);
|
|
if (auto rootValueAndName =
|
|
VariableNameInferrer::inferNameAndRoot(transferOp->get())) {
|
|
diagnosticEmitter.emitNamedIsolationCrossingDueToCapture(
|
|
RegularLocation(std::get<0>(p).getLoc()), rootValueAndName->first,
|
|
state.isolationInfo.getIsolationInfo(), std::get<2>(p));
|
|
continue;
|
|
}
|
|
|
|
diagnosticEmitter.emitTypedIsolationCrossingDueToCapture(
|
|
RegularLocation(std::get<0>(p).getLoc()), baseInferredType,
|
|
std::get<2>(p));
|
|
}
|
|
|
|
return emittedDiagnostic;
|
|
}
|
|
|
|
void UseAfterTransferDiagnosticInferrer::initForApply(Operand *op,
|
|
ApplyExpr *sourceApply) {
|
|
auto isolationCrossing = sourceApply->getIsolationCrossing().value();
|
|
|
|
// Grab out full apply site and see if we can find a better expr.
|
|
SILInstruction *i = const_cast<SILInstruction *>(op->getUser());
|
|
auto fai = FullApplySite::isa(i);
|
|
|
|
assert(!fai.getArgumentConvention(*op).isIndirectOutParameter() &&
|
|
"An indirect out parameter is never transferred");
|
|
auto *foundExpr = inferArgumentExprFromApplyExpr(sourceApply, fai, op);
|
|
|
|
auto inferredArgType =
|
|
foundExpr ? foundExpr->findOriginalType() : baseInferredType;
|
|
diagnosticEmitter.emitTypedIsolationCrossing(baseLoc, inferredArgType,
|
|
isolationCrossing);
|
|
}
|
|
|
|
/// This walker visits an AutoClosureExpr and looks for uses of a specific
|
|
/// captured value. We want to error on the uses in the autoclosure.
|
|
struct UseAfterTransferDiagnosticInferrer::AutoClosureWalker : ASTWalker {
|
|
UseAfterTransferDiagnosticInferrer &foundTypeInfo;
|
|
ValueDecl *targetDecl;
|
|
SILIsolationInfo targetDeclIsolationInfo;
|
|
SmallPtrSet<Expr *, 8> visitedCallExprDeclRefExprs;
|
|
|
|
AutoClosureWalker(UseAfterTransferDiagnosticInferrer &foundTypeInfo,
|
|
ValueDecl *targetDecl,
|
|
SILIsolationInfo targetDeclIsolationInfo)
|
|
: foundTypeInfo(foundTypeInfo), targetDecl(targetDecl),
|
|
targetDeclIsolationInfo(targetDeclIsolationInfo) {}
|
|
|
|
Expr *lookThroughArgExpr(Expr *expr) {
|
|
while (true) {
|
|
if (auto *memberRefExpr = dyn_cast<MemberRefExpr>(expr)) {
|
|
expr = memberRefExpr->getBase();
|
|
continue;
|
|
}
|
|
|
|
if (auto *cvt = dyn_cast<ImplicitConversionExpr>(expr)) {
|
|
expr = cvt->getSubExpr();
|
|
continue;
|
|
}
|
|
|
|
if (auto *e = dyn_cast<ForceValueExpr>(expr)) {
|
|
expr = e->getSubExpr();
|
|
continue;
|
|
}
|
|
|
|
if (auto *t = dyn_cast<TupleElementExpr>(expr)) {
|
|
expr = t->getBase();
|
|
continue;
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
}
|
|
|
|
PreWalkResult<Expr *> walkToExprPre(Expr *expr) override {
|
|
if (auto *declRef = dyn_cast<DeclRefExpr>(expr)) {
|
|
// If this decl ref expr was not visited as part of a callExpr and is our
|
|
// target decl... emit a simple async let error.
|
|
if (!visitedCallExprDeclRefExprs.count(declRef)) {
|
|
if (declRef->getDecl() == targetDecl) {
|
|
foundTypeInfo.diagnosticEmitter
|
|
.emitNamedAsyncLetNoIsolationCrossingError(
|
|
foundTypeInfo.baseLoc, targetDecl->getBaseIdentifier());
|
|
return Action::Continue(expr);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we have a call expr, see if any of its arguments will cause our sent
|
|
// value to be transferred into another isolation domain.
|
|
if (auto *callExpr = dyn_cast<CallExpr>(expr)) {
|
|
// Search callExpr's arguments to see if we have our targetDecl.
|
|
auto *argList = callExpr->getArgs();
|
|
for (auto pair : llvm::enumerate(argList->getArgExprs())) {
|
|
auto *arg = lookThroughArgExpr(pair.value());
|
|
auto *declRef = dyn_cast<DeclRefExpr>(arg);
|
|
if (!declRef)
|
|
continue;
|
|
|
|
if (declRef->getDecl() != targetDecl)
|
|
continue;
|
|
|
|
// Found our target!
|
|
visitedCallExprDeclRefExprs.insert(declRef);
|
|
|
|
auto isolationCrossing = callExpr->getIsolationCrossing();
|
|
|
|
// If we do not have an isolation crossing, then we must be just sending
|
|
// a value in a nonisolated fashion into an async let. So emit the
|
|
// simple async let error.
|
|
if (!isolationCrossing) {
|
|
foundTypeInfo.diagnosticEmitter
|
|
.emitNamedAsyncLetNoIsolationCrossingError(
|
|
foundTypeInfo.baseLoc, targetDecl->getBaseIdentifier());
|
|
continue;
|
|
}
|
|
|
|
// Otherwise, we are calling an actor isolated function in the async
|
|
// let. Emit a better error.
|
|
|
|
// See if we can find a valueDecl/name for our callee so we can
|
|
// emit a nicer error.
|
|
ConcreteDeclRef concreteDecl =
|
|
callExpr->getDirectCallee()->getReferencedDecl();
|
|
|
|
// If we do not find a direct one, see if we are calling a method
|
|
// on a nominal type.
|
|
if (!concreteDecl) {
|
|
if (auto *dot =
|
|
dyn_cast<DotSyntaxCallExpr>(callExpr->getDirectCallee())) {
|
|
concreteDecl = dot->getSemanticFn()->getReferencedDecl();
|
|
}
|
|
}
|
|
|
|
if (!concreteDecl)
|
|
continue;
|
|
|
|
auto *valueDecl = concreteDecl.getDecl();
|
|
assert(valueDecl && "Should be non-null if concreteDecl is valid");
|
|
|
|
if (auto isolationCrossing = callExpr->getIsolationCrossing()) {
|
|
// If we have an isolation crossing, use that information.
|
|
if (valueDecl->hasName()) {
|
|
foundTypeInfo.diagnosticEmitter.emitNamedIsolationCrossingError(
|
|
foundTypeInfo.baseLoc, targetDecl->getBaseIdentifier(),
|
|
targetDeclIsolationInfo, *isolationCrossing,
|
|
valueDecl->getName(), valueDecl->getDescriptiveKind());
|
|
continue;
|
|
}
|
|
|
|
// Otherwise default back to the "callee" error.
|
|
foundTypeInfo.diagnosticEmitter.emitNamedIsolationCrossingError(
|
|
foundTypeInfo.baseLoc, targetDecl->getBaseIdentifier(),
|
|
targetDeclIsolationInfo, *isolationCrossing);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Action::Continue(expr);
|
|
}
|
|
};
|
|
|
|
void UseAfterTransferDiagnosticInferrer::infer() {
|
|
// Otherwise, see if our operand's instruction is a transferring parameter.
|
|
if (auto fas = FullApplySite::isa(transferOp->getUser())) {
|
|
assert(!fas.getArgumentConvention(*transferOp).isIndirectOutParameter() &&
|
|
"We should never transfer an indirect out parameter");
|
|
if (fas.getArgumentParameterInfo(*transferOp)
|
|
.hasOption(SILParameterInfo::Sending)) {
|
|
|
|
// First try to do the named diagnostic if we can find a name.
|
|
if (auto rootValueAndName =
|
|
VariableNameInferrer::inferNameAndRoot(transferOp->get())) {
|
|
return diagnosticEmitter.emitNamedUseOfStronglyTransferredValue(
|
|
baseLoc, rootValueAndName->first);
|
|
}
|
|
|
|
// Otherwise, emit the typed diagnostic.
|
|
return diagnosticEmitter.emitTypedUseOfStronglyTransferredValue(
|
|
baseLoc, baseInferredType);
|
|
}
|
|
}
|
|
|
|
auto loc = transferOp->getUser()->getLoc();
|
|
|
|
// If we have a partial_apply that is actor isolated, see if we found a
|
|
// transfer error due to us transferring a value into it.
|
|
if (auto *ace = loc.getAsASTNode<AbstractClosureExpr>()) {
|
|
if (ace->getActorIsolation().isActorIsolated()) {
|
|
if (initForIsolatedPartialApply(transferOp, ace)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auto *sourceApply = loc.getAsASTNode<ApplyExpr>()) {
|
|
// Before we do anything further, see if we can find a name and emit a name
|
|
// error.
|
|
if (auto rootValueAndName =
|
|
VariableNameInferrer::inferNameAndRoot(transferOp->get())) {
|
|
auto &state = transferringOpToStateMap.get(transferOp);
|
|
return diagnosticEmitter.emitNamedIsolationCrossingError(
|
|
baseLoc, rootValueAndName->first,
|
|
state.isolationInfo.getIsolationInfo(),
|
|
*sourceApply->getIsolationCrossing());
|
|
}
|
|
|
|
// Otherwise, try to infer from the ApplyExpr.
|
|
return initForApply(transferOp, sourceApply);
|
|
}
|
|
|
|
if (auto fas = FullApplySite::isa(transferOp->getUser())) {
|
|
if (auto isolationCrossing = fas.getIsolationCrossing()) {
|
|
return diagnosticEmitter.emitTypedIsolationCrossing(
|
|
baseLoc, baseInferredType, *isolationCrossing);
|
|
}
|
|
}
|
|
|
|
auto *autoClosureExpr = loc.getAsASTNode<AutoClosureExpr>();
|
|
if (!autoClosureExpr) {
|
|
return diagnosticEmitter.emitUnknownPatternError();
|
|
}
|
|
|
|
auto *i = transferOp->getUser();
|
|
auto pai = ApplySite::isa(i);
|
|
unsigned captureIndex = pai.getAppliedArgIndex(*transferOp);
|
|
|
|
auto &state = transferringOpToStateMap.get(transferOp);
|
|
auto captureInfo =
|
|
autoClosureExpr->getCaptureInfo().getCaptures()[captureIndex];
|
|
auto *captureDecl = captureInfo.getDecl();
|
|
AutoClosureWalker walker(*this, captureDecl,
|
|
state.isolationInfo.getIsolationInfo());
|
|
autoClosureExpr->walk(walker);
|
|
}
|
|
|
|
// Top level entrypoint for use after transfer diagnostics.
|
|
void TransferNonSendableImpl::emitUseAfterTransferDiagnostics() {
|
|
auto *function = regionInfo->getFunction();
|
|
BasicBlockData<BlockLivenessInfo> blockLivenessInfo(function);
|
|
// We use a generation counter so we can lazily reset blockLivenessInfo
|
|
// since we cannot clear it without iterating over it.
|
|
unsigned blockLivenessInfoGeneration = 0;
|
|
|
|
if (transferOpToRequireInstMultiMap.empty())
|
|
return;
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Emitting use after transfer diagnostics.\n");
|
|
|
|
for (auto [transferOp, requireInsts] :
|
|
transferOpToRequireInstMultiMap.getRange()) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "Transfer Op. Number: " << transferOp->getOperandNumber()
|
|
<< ". User: " << *transferOp->getUser());
|
|
|
|
// Then look for our requires before we emit any error. We want to emit a
|
|
// single we don't understand error if we do not find the require.
|
|
bool didEmitRequireNote = false;
|
|
InstructionSet requireInstsUnique(function);
|
|
RequireLiveness liveness(blockLivenessInfoGeneration, transferOp,
|
|
blockLivenessInfo);
|
|
++blockLivenessInfoGeneration;
|
|
liveness.process(requireInsts);
|
|
|
|
SmallVector<RequireInst, 8> requireInstsForError;
|
|
for (auto require : requireInsts) {
|
|
// We can have multiple of the same require insts if we had a require
|
|
// and an assign from the same instruction. Our liveness checking
|
|
// above doesn't care about that, but we still need to make sure we do
|
|
// not emit twice.
|
|
if (!requireInstsUnique.insert(*require))
|
|
continue;
|
|
|
|
// If this was not a last require, do not emit an error.
|
|
if (!liveness.finalRequires.contains(*require))
|
|
continue;
|
|
|
|
requireInstsForError.push_back(require);
|
|
didEmitRequireNote = true;
|
|
}
|
|
|
|
// If we did not emit a require, emit an "unknown pattern" error that
|
|
// tells the user to file a bug. This importantly ensures that we can
|
|
// guarantee that we always find the require if we successfully compile.
|
|
if (!didEmitRequireNote) {
|
|
if (shouldAbortOnUnknownPatternMatchError()) {
|
|
llvm::report_fatal_error(
|
|
"RegionIsolation: Aborting on unknown pattern match error");
|
|
}
|
|
|
|
diagnoseError(transferOp, diag::regionbasedisolation_unknown_pattern);
|
|
continue;
|
|
}
|
|
|
|
UseAfterTransferDiagnosticInferrer diagnosticInferrer(
|
|
transferOp, requireInstsForError, regionInfo->getValueMap(),
|
|
regionInfo->getTransferringOpToStateMap());
|
|
diagnosticInferrer.infer();
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Transfer NonTransferrable Diagnostic Inference
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
class TransferNonTransferrableDiagnosticEmitter {
|
|
TransferredNonTransferrableInfo info;
|
|
bool emittedErrorDiagnostic = false;
|
|
|
|
public:
|
|
TransferNonTransferrableDiagnosticEmitter(
|
|
TransferredNonTransferrableInfo info)
|
|
: info(info) {}
|
|
|
|
~TransferNonTransferrableDiagnosticEmitter() {
|
|
if (!emittedErrorDiagnostic) {
|
|
emitUnknownPatternError();
|
|
}
|
|
}
|
|
|
|
Operand *getOperand() const { return info.transferredOperand; }
|
|
|
|
SILValue getNonTransferrableValue() const {
|
|
return info.nonTransferrable.dyn_cast<SILValue>();
|
|
}
|
|
|
|
SILInstruction *getNonTransferringActorIntroducingInst() const {
|
|
return info.nonTransferrable.dyn_cast<SILInstruction *>();
|
|
}
|
|
|
|
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
|
|
return getDiagnosticBehaviorLimitForValue(info.transferredOperand->get());
|
|
}
|
|
|
|
/// If we can find a callee decl name, return that. None otherwise.
|
|
std::optional<std::pair<DescriptiveDeclKind, DeclName>>
|
|
getTransferringCalleeInfo() const {
|
|
return getTransferringApplyCalleeInfo(info.transferredOperand->getUser());
|
|
}
|
|
|
|
SILLocation getLoc() const {
|
|
return info.transferredOperand->getUser()->getLoc();
|
|
}
|
|
|
|
/// Return the isolation region info for \p getNonTransferrableValue().
|
|
SILDynamicMergedIsolationInfo getIsolationRegionInfo() const {
|
|
return info.isolationRegionInfo;
|
|
}
|
|
|
|
void emitUnknownPatternError() {
|
|
if (shouldAbortOnUnknownPatternMatchError()) {
|
|
llvm::report_fatal_error(
|
|
"RegionIsolation: Aborting on unknown pattern match error");
|
|
}
|
|
|
|
diagnoseError(getOperand()->getUser(),
|
|
diag::regionbasedisolation_unknown_pattern)
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
}
|
|
|
|
void emitUnknownUse(SILLocation loc) {
|
|
// TODO: This will eventually be an unknown pattern error.
|
|
diagnoseError(loc,
|
|
diag::regionbasedisolation_task_or_actor_isolated_transferred)
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
}
|
|
|
|
void emitFunctionArgumentApply(SILLocation loc, Type type,
|
|
ApplyIsolationCrossing crossing) {
|
|
SmallString<64> descriptiveKindStr;
|
|
{
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
getIsolationRegionInfo().printForDiagnostics(os);
|
|
}
|
|
diagnoseError(loc, diag::regionbasedisolation_arg_transferred,
|
|
descriptiveKindStr, type, crossing.getCalleeIsolation())
|
|
.highlight(getOperand()->getUser()->getLoc().getSourceRange())
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
}
|
|
|
|
void emitNamedFunctionArgumentClosure(SILLocation loc, Identifier name,
|
|
ApplyIsolationCrossing crossing) {
|
|
emitNamedOnlyError(loc, name);
|
|
SmallString<64> descriptiveKindStr;
|
|
{
|
|
if (!getIsolationRegionInfo().isDisconnected()) {
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
getIsolationRegionInfo().printForDiagnostics(os);
|
|
os << ' ';
|
|
}
|
|
}
|
|
diagnoseNote(loc,
|
|
diag::regionbasedisolation_named_isolated_closure_yields_race,
|
|
descriptiveKindStr, name, crossing.getCalleeIsolation(),
|
|
crossing.getCallerIsolation())
|
|
.highlight(loc.getSourceRange());
|
|
}
|
|
|
|
void emitFunctionArgumentApplyStronglyTransferred(SILLocation loc,
|
|
Type type) {
|
|
SmallString<64> descriptiveKindStr;
|
|
{
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
getIsolationRegionInfo().printForDiagnostics(os);
|
|
}
|
|
auto diag =
|
|
diag::regionbasedisolation_arg_passed_to_strongly_transferred_param;
|
|
diagnoseError(loc, diag, descriptiveKindStr, type)
|
|
.highlight(getOperand()->getUser()->getLoc().getSourceRange())
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
}
|
|
|
|
void emitNamedOnlyError(SILLocation loc, Identifier name) {
|
|
diagnoseError(loc, diag::regionbasedisolation_named_transfer_yields_race,
|
|
name)
|
|
.highlight(getOperand()->getUser()->getLoc().getSourceRange())
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
}
|
|
|
|
void emitNamedAsyncLetCapture(SILLocation loc, Identifier name,
|
|
SILIsolationInfo transferredValueIsolation) {
|
|
assert(!getIsolationRegionInfo().isDisconnected() &&
|
|
"Should never be disconnected?!");
|
|
emitNamedOnlyError(loc, name);
|
|
|
|
SmallString<64> descriptiveKindStr;
|
|
{
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
getIsolationRegionInfo().printForDiagnostics(os);
|
|
}
|
|
|
|
diagnoseNote(loc,
|
|
diag::regionbasedisolation_named_transfer_nt_asynclet_capture,
|
|
name, descriptiveKindStr)
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
}
|
|
|
|
void emitNamedIsolation(SILLocation loc, Identifier name,
|
|
ApplyIsolationCrossing isolationCrossing) {
|
|
emitNamedOnlyError(loc, name);
|
|
SmallString<64> descriptiveKindStr;
|
|
SmallString<64> descriptiveKindStrWithSpace;
|
|
{
|
|
if (!getIsolationRegionInfo().isDisconnected()) {
|
|
{
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
getIsolationRegionInfo().printForDiagnostics(os);
|
|
}
|
|
descriptiveKindStrWithSpace = descriptiveKindStr;
|
|
descriptiveKindStrWithSpace.push_back(' ');
|
|
}
|
|
}
|
|
if (auto calleeInfo = getTransferringCalleeInfo()) {
|
|
diagnoseNote(
|
|
loc,
|
|
diag::regionbasedisolation_named_transfer_non_transferrable_callee,
|
|
name, descriptiveKindStrWithSpace,
|
|
isolationCrossing.getCalleeIsolation(), calleeInfo->first,
|
|
calleeInfo->second, descriptiveKindStr);
|
|
} else {
|
|
diagnoseNote(loc,
|
|
diag::regionbasedisolation_named_transfer_non_transferrable,
|
|
name, descriptiveKindStrWithSpace,
|
|
isolationCrossing.getCalleeIsolation(), descriptiveKindStr);
|
|
}
|
|
}
|
|
|
|
void emitNamedFunctionArgumentApplyStronglyTransferred(SILLocation loc,
|
|
Identifier varName) {
|
|
emitNamedOnlyError(loc, varName);
|
|
SmallString<64> descriptiveKindStr;
|
|
{
|
|
if (!getIsolationRegionInfo().isDisconnected()) {
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
getIsolationRegionInfo().printForDiagnostics(os);
|
|
os << ' ';
|
|
}
|
|
}
|
|
auto diag = diag::regionbasedisolation_named_transfer_into_sending_param;
|
|
diagnoseNote(loc, diag, descriptiveKindStr, varName);
|
|
}
|
|
|
|
void emitNamedTransferringReturn(SILLocation loc, Identifier varName) {
|
|
emitNamedOnlyError(loc, varName);
|
|
SmallString<64> descriptiveKindStr;
|
|
SmallString<64> descriptiveKindStrWithSpace;
|
|
{
|
|
if (!getIsolationRegionInfo().isDisconnected()) {
|
|
{
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
getIsolationRegionInfo().printForDiagnostics(os);
|
|
}
|
|
descriptiveKindStrWithSpace = descriptiveKindStr;
|
|
descriptiveKindStrWithSpace.push_back(' ');
|
|
}
|
|
}
|
|
auto diag =
|
|
diag::regionbasedisolation_named_notransfer_transfer_into_result;
|
|
diagnoseNote(loc, diag, descriptiveKindStrWithSpace, varName,
|
|
descriptiveKindStr);
|
|
}
|
|
|
|
private:
|
|
ASTContext &getASTContext() const {
|
|
return getOperand()->getFunction()->getASTContext();
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
emittedErrorDiagnostic = true;
|
|
return std::move(getASTContext()
|
|
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
|
|
.warnUntilSwiftVersion(6));
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
|
|
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
};
|
|
|
|
class TransferNonTransferrableDiagnosticInferrer {
|
|
struct AutoClosureWalker;
|
|
|
|
TransferNonTransferrableDiagnosticEmitter diagnosticEmitter;
|
|
|
|
public:
|
|
TransferNonTransferrableDiagnosticInferrer(
|
|
TransferredNonTransferrableInfo info)
|
|
: diagnosticEmitter(info) {}
|
|
|
|
/// Gathers diagnostics. Returns false if we emitted a "I don't understand
|
|
/// error". If we emit such an error, we should bail without emitting any
|
|
/// further diagnostics, since we may not have any diagnostics or be in an
|
|
/// inconcistent state.
|
|
bool run();
|
|
|
|
private:
|
|
/// \p actualCallerIsolation is used to override the caller isolation we use
|
|
/// when emitting the error if the closure would have the incorrect one.
|
|
bool initForIsolatedPartialApply(
|
|
Operand *op, AbstractClosureExpr *ace,
|
|
std::optional<ActorIsolation> actualCallerIsolation = {});
|
|
};
|
|
|
|
} // namespace
|
|
|
|
bool TransferNonTransferrableDiagnosticInferrer::initForIsolatedPartialApply(
|
|
Operand *op, AbstractClosureExpr *ace,
|
|
std::optional<ActorIsolation> actualCallerIsolation) {
|
|
SmallVector<std::tuple<CapturedValue, unsigned, ApplyIsolationCrossing>, 8>
|
|
foundCapturedIsolationCrossing;
|
|
ace->getIsolationCrossing(foundCapturedIsolationCrossing);
|
|
if (foundCapturedIsolationCrossing.empty())
|
|
return false;
|
|
|
|
unsigned opIndex = ApplySite(op->getUser()).getAppliedArgIndex(*op);
|
|
for (auto &p : foundCapturedIsolationCrossing) {
|
|
if (std::get<1>(p) == opIndex) {
|
|
auto loc = RegularLocation(std::get<0>(p).getLoc());
|
|
auto crossing = std::get<2>(p);
|
|
auto declIsolation = crossing.getCallerIsolation();
|
|
auto closureIsolation = crossing.getCalleeIsolation();
|
|
if (!bool(declIsolation) && actualCallerIsolation) {
|
|
declIsolation = *actualCallerIsolation;
|
|
}
|
|
diagnosticEmitter.emitNamedFunctionArgumentClosure(
|
|
loc, std::get<0>(p).getDecl()->getBaseIdentifier(),
|
|
ApplyIsolationCrossing(declIsolation, closureIsolation));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// This walker visits an AutoClosureExpr and looks for uses of a specific
|
|
/// captured value. We want to error on the uses in the autoclosure.
|
|
struct TransferNonTransferrableDiagnosticInferrer::AutoClosureWalker
|
|
: ASTWalker {
|
|
TransferNonTransferrableDiagnosticEmitter &foundTypeInfo;
|
|
ValueDecl *targetDecl;
|
|
SILIsolationInfo targetDeclIsolationInfo;
|
|
SmallPtrSet<Expr *, 8> visitedCallExprDeclRefExprs;
|
|
SILLocation captureLoc;
|
|
bool isAsyncLet;
|
|
|
|
AutoClosureWalker(TransferNonTransferrableDiagnosticEmitter &foundTypeInfo,
|
|
ValueDecl *targetDecl,
|
|
SILIsolationInfo targetDeclIsolationInfo,
|
|
SILLocation captureLoc, bool isAsyncLet)
|
|
: foundTypeInfo(foundTypeInfo), targetDecl(targetDecl),
|
|
targetDeclIsolationInfo(targetDeclIsolationInfo),
|
|
captureLoc(captureLoc), isAsyncLet(isAsyncLet) {}
|
|
|
|
Expr *lookThroughArgExpr(Expr *expr) {
|
|
while (true) {
|
|
if (auto *memberRefExpr = dyn_cast<MemberRefExpr>(expr)) {
|
|
expr = memberRefExpr->getBase();
|
|
continue;
|
|
}
|
|
|
|
if (auto *cvt = dyn_cast<ImplicitConversionExpr>(expr)) {
|
|
expr = cvt->getSubExpr();
|
|
continue;
|
|
}
|
|
|
|
if (auto *e = dyn_cast<ForceValueExpr>(expr)) {
|
|
expr = e->getSubExpr();
|
|
continue;
|
|
}
|
|
|
|
if (auto *t = dyn_cast<TupleElementExpr>(expr)) {
|
|
expr = t->getBase();
|
|
continue;
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
}
|
|
|
|
PreWalkResult<Expr *> walkToExprPre(Expr *expr) override {
|
|
if (auto *declRef = dyn_cast<DeclRefExpr>(expr)) {
|
|
// If this decl ref expr was not visited as part of a callExpr and is our
|
|
// target decl... emit a simple async let error.
|
|
//
|
|
// This occurs if we do:
|
|
//
|
|
// ```
|
|
// let x = ...
|
|
// async let y = x
|
|
// ```
|
|
if (declRef->getDecl() == targetDecl) {
|
|
foundTypeInfo.emitNamedAsyncLetCapture(captureLoc,
|
|
targetDecl->getBaseIdentifier(),
|
|
targetDeclIsolationInfo);
|
|
return Action::Continue(expr);
|
|
}
|
|
}
|
|
|
|
return Action::Continue(expr);
|
|
}
|
|
};
|
|
|
|
bool TransferNonTransferrableDiagnosticInferrer::run() {
|
|
// We need to find the isolation info.
|
|
auto *op = diagnosticEmitter.getOperand();
|
|
auto loc = op->getUser()->getLoc();
|
|
|
|
if (auto *sourceApply = loc.getAsASTNode<ApplyExpr>()) {
|
|
// First see if we have a transferring argument.
|
|
if (auto fas = FullApplySite::isa(op->getUser())) {
|
|
if (fas.getArgumentParameterInfo(*op).hasOption(
|
|
SILParameterInfo::Sending)) {
|
|
|
|
// See if we can infer a name from the value.
|
|
SmallString<64> resultingName;
|
|
if (auto varName = VariableNameInferrer::inferName(op->get())) {
|
|
diagnosticEmitter.emitNamedFunctionArgumentApplyStronglyTransferred(
|
|
loc, *varName);
|
|
return true;
|
|
}
|
|
|
|
Type type = op->get()->getType().getASTType();
|
|
if (auto *inferredArgExpr =
|
|
inferArgumentExprFromApplyExpr(sourceApply, fas, op)) {
|
|
type = inferredArgExpr->findOriginalType();
|
|
}
|
|
diagnosticEmitter.emitFunctionArgumentApplyStronglyTransferred(loc,
|
|
type);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// First try to get the apply from the isolation crossing.
|
|
auto isolation = sourceApply->getIsolationCrossing();
|
|
|
|
// If we could not infer an isolation...
|
|
if (!isolation) {
|
|
// Otherwise, emit a "we don't know error" that tells the user to file a
|
|
// bug.
|
|
diagnosticEmitter.emitUnknownPatternError();
|
|
return false;
|
|
}
|
|
assert(isolation && "Expected non-null");
|
|
|
|
// Then if we are calling a closure expr. If so, we should use the loc of
|
|
// the closure.
|
|
if (auto *closureExpr =
|
|
dyn_cast<AbstractClosureExpr>(sourceApply->getFn())) {
|
|
initForIsolatedPartialApply(op, closureExpr,
|
|
isolation->getCallerIsolation());
|
|
return true;
|
|
}
|
|
|
|
// See if we can infer a name from the value.
|
|
SmallString<64> resultingName;
|
|
if (auto name = VariableNameInferrer::inferName(op->get())) {
|
|
diagnosticEmitter.emitNamedIsolation(loc, *name, *isolation);
|
|
return true;
|
|
}
|
|
|
|
// Attempt to find the specific sugared ASTType if we can to emit a better
|
|
// diagnostic.
|
|
Type type = op->get()->getType().getASTType();
|
|
if (auto fas = FullApplySite::isa(op->getUser())) {
|
|
if (auto *inferredArgExpr =
|
|
inferArgumentExprFromApplyExpr(sourceApply, fas, op)) {
|
|
type = inferredArgExpr->findOriginalType();
|
|
}
|
|
}
|
|
|
|
diagnosticEmitter.emitFunctionArgumentApply(loc, type, *isolation);
|
|
return true;
|
|
}
|
|
|
|
if (auto *ace = loc.getAsASTNode<AbstractClosureExpr>()) {
|
|
if (ace->getActorIsolation().isActorIsolated()) {
|
|
if (initForIsolatedPartialApply(op, ace)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// See if we are in SIL and have an apply site specified isolation.
|
|
if (auto fas = FullApplySite::isa(op->getUser())) {
|
|
if (auto isolation = fas.getIsolationCrossing()) {
|
|
diagnosticEmitter.emitFunctionArgumentApply(
|
|
loc, op->get()->getType().getASTType(), *isolation);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (auto *ri = dyn_cast<ReturnInst>(op->getUser())) {
|
|
auto fType = ri->getFunction()->getLoweredFunctionType();
|
|
if (fType->getNumResults() &&
|
|
fType->getResults()[0].hasOption(SILResultInfo::IsSending)) {
|
|
assert(llvm::all_of(fType->getResults(),
|
|
[](SILResultInfo resultInfo) {
|
|
return resultInfo.hasOption(
|
|
SILResultInfo::IsSending);
|
|
}) &&
|
|
"All result info must be the same... if that changes... update "
|
|
"this code!");
|
|
SmallString<64> resultingName;
|
|
if (auto name = VariableNameInferrer::inferName(op->get())) {
|
|
diagnosticEmitter.emitNamedTransferringReturn(loc, *name);
|
|
return true;
|
|
}
|
|
} else {
|
|
assert(llvm::none_of(fType->getResults(),
|
|
[](SILResultInfo resultInfo) {
|
|
return resultInfo.hasOption(
|
|
SILResultInfo::IsSending);
|
|
}) &&
|
|
"All result info must be the same... if that changes... update "
|
|
"this code!");
|
|
}
|
|
}
|
|
|
|
// If we are failing due to an autoclosure... see if we can find the captured
|
|
// value that is causing the issue.
|
|
if (auto *autoClosureExpr = loc.getAsASTNode<AutoClosureExpr>()) {
|
|
// To split up this work, we only do this for async let for now.
|
|
if (autoClosureExpr->getThunkKind() == AutoClosureExpr::Kind::AsyncLet) {
|
|
auto *i = op->getUser();
|
|
auto pai = ApplySite::isa(i);
|
|
unsigned captureIndex = pai.getAppliedArgIndex(*op);
|
|
auto captureInfo =
|
|
autoClosureExpr->getCaptureInfo().getCaptures()[captureIndex];
|
|
auto loc = RegularLocation(captureInfo.getLoc(), false /*implicit*/);
|
|
AutoClosureWalker walker(
|
|
diagnosticEmitter, captureInfo.getDecl(),
|
|
diagnosticEmitter.getIsolationRegionInfo().getIsolationInfo(), loc,
|
|
autoClosureExpr->getThunkKind() == AutoClosureExpr::Kind::AsyncLet);
|
|
autoClosureExpr->walk(walker);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
diagnosticEmitter.emitUnknownUse(loc);
|
|
return true;
|
|
}
|
|
|
|
// Top level emission for transfer non transferable diagnostic.
|
|
void TransferNonSendableImpl::emitTransferredNonTransferrableDiagnostics() {
|
|
if (transferredNonTransferrableInfoList.empty())
|
|
return;
|
|
|
|
LLVM_DEBUG(
|
|
llvm::dbgs() << "Emitting transfer non transferrable diagnostics.\n");
|
|
|
|
for (auto info : transferredNonTransferrableInfoList) {
|
|
TransferNonTransferrableDiagnosticInferrer diagnosticInferrer(info);
|
|
diagnosticInferrer.run();
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: InOutSendingNotDisconnected Error Emitter
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
class InOutSendingNotDisconnectedDiagnosticEmitter {
|
|
InOutSendingNotDisconnectedInfo info;
|
|
bool emittedErrorDiagnostic = false;
|
|
|
|
public:
|
|
InOutSendingNotDisconnectedDiagnosticEmitter(
|
|
InOutSendingNotDisconnectedInfo info)
|
|
: info(info) {}
|
|
|
|
~InOutSendingNotDisconnectedDiagnosticEmitter() {
|
|
// If we were supposed to emit a diagnostic and didn't emit an unknown
|
|
// pattern error.
|
|
if (!emittedErrorDiagnostic)
|
|
emitUnknownPatternError();
|
|
}
|
|
|
|
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
|
|
return getDiagnosticBehaviorLimitForValue(info.inoutSendingParam);
|
|
}
|
|
|
|
void emitUnknownPatternError() {
|
|
if (shouldAbortOnUnknownPatternMatchError()) {
|
|
llvm::report_fatal_error(
|
|
"RegionIsolation: Aborting on unknown pattern match error");
|
|
}
|
|
|
|
diagnoseError(info.functionExitingInst,
|
|
diag::regionbasedisolation_unknown_pattern)
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
}
|
|
|
|
void emit();
|
|
|
|
ASTContext &getASTContext() const {
|
|
return info.functionExitingInst->getFunction()->getASTContext();
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
emittedErrorDiagnostic = true;
|
|
return std::move(getASTContext()
|
|
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
|
|
.warnUntilSwiftVersion(6));
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
|
|
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void InOutSendingNotDisconnectedDiagnosticEmitter::emit() {
|
|
// We should always be able to find a name for an inout sending param. If we
|
|
// do not, emit an unknown pattern error.
|
|
auto varName = VariableNameInferrer::inferName(info.inoutSendingParam);
|
|
if (!varName) {
|
|
return emitUnknownPatternError();
|
|
}
|
|
|
|
// Then emit the note with greater context.
|
|
SmallString<64> descriptiveKindStr;
|
|
{
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
info.actorIsolatedRegionInfo.printForDiagnostics(os);
|
|
os << ' ';
|
|
}
|
|
|
|
diagnoseError(
|
|
info.functionExitingInst,
|
|
diag::regionbasedisolation_inout_sending_cannot_be_actor_isolated,
|
|
*varName, descriptiveKindStr)
|
|
.limitBehaviorIf(getBehaviorLimit());
|
|
|
|
diagnoseNote(
|
|
info.functionExitingInst,
|
|
diag::regionbasedisolation_inout_sending_cannot_be_actor_isolated_note,
|
|
*varName, descriptiveKindStr);
|
|
}
|
|
|
|
void TransferNonSendableImpl::emitInOutSendingNotDisconnectedInfoList() {
|
|
for (auto &info : inoutSendingNotDisconnectedInfoList) {
|
|
InOutSendingNotDisconnectedDiagnosticEmitter emitter(info);
|
|
emitter.emit();
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Diagnostic Evaluator
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
struct DiagnosticEvaluator final
|
|
: PartitionOpEvaluatorBaseImpl<DiagnosticEvaluator> {
|
|
RegionAnalysisFunctionInfo *info;
|
|
SmallFrozenMultiMap<Operand *, RequireInst, 8>
|
|
&transferOpToRequireInstMultiMap;
|
|
|
|
/// First value is the operand that was transferred... second value is the
|
|
/// non-transferrable value in the same region as that value. The second value
|
|
/// is what is non-transferrable.
|
|
SmallVectorImpl<TransferredNonTransferrableInfo> &transferredNonTransferrable;
|
|
|
|
/// A list of state that tracks specific 'inout sending' parameters that were
|
|
/// actor isolated on function exit with the necessary state to emit the
|
|
/// error.
|
|
SmallVectorImpl<InOutSendingNotDisconnectedInfo>
|
|
&inoutSendingNotDisconnectedInfoList;
|
|
|
|
DiagnosticEvaluator(Partition &workingPartition,
|
|
RegionAnalysisFunctionInfo *info,
|
|
SmallFrozenMultiMap<Operand *, RequireInst, 8>
|
|
&transferOpToRequireInstMultiMap,
|
|
SmallVectorImpl<TransferredNonTransferrableInfo>
|
|
&transferredNonTransferrable,
|
|
SmallVectorImpl<InOutSendingNotDisconnectedInfo>
|
|
&inoutSendingNotDisconnectedInfoList,
|
|
TransferringOperandToStateMap &operandToStateMap)
|
|
: PartitionOpEvaluatorBaseImpl(
|
|
workingPartition, info->getOperandSetFactory(), operandToStateMap),
|
|
info(info),
|
|
transferOpToRequireInstMultiMap(transferOpToRequireInstMultiMap),
|
|
transferredNonTransferrable(transferredNonTransferrable),
|
|
inoutSendingNotDisconnectedInfoList(
|
|
inoutSendingNotDisconnectedInfoList) {}
|
|
|
|
void handleLocalUseAfterTransfer(const PartitionOp &partitionOp,
|
|
Element transferredVal,
|
|
Operand *transferringOp) const {
|
|
auto &operandState = operandToStateMap.get(transferringOp);
|
|
// Ignore this if we have a gep like instruction that is returning a
|
|
// sendable type and transferringOp was not set with closure
|
|
// capture.
|
|
if (auto *svi =
|
|
dyn_cast<SingleValueInstruction>(partitionOp.getSourceInst())) {
|
|
if (isa<TupleElementAddrInst, StructElementAddrInst>(svi) &&
|
|
!SILIsolationInfo::isNonSendableType(svi->getType(),
|
|
svi->getFunction())) {
|
|
bool isCapture = operandState.isClosureCaptured;
|
|
if (!isCapture) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto rep = info->getValueMap().getRepresentative(transferredVal);
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " Emitting Use After Transfer Error!\n"
|
|
<< " Transferring Inst: " << *transferringOp->getUser()
|
|
<< " Transferring Op Value: " << transferringOp->get()
|
|
<< " Require Inst: " << *partitionOp.getSourceInst()
|
|
<< " ID: %%" << transferredVal << "\n"
|
|
<< " Rep: " << *rep << " Transferring Op Num: "
|
|
<< transferringOp->getOperandNumber() << '\n');
|
|
transferOpToRequireInstMultiMap.insert(
|
|
transferringOp,
|
|
RequireInst::forUseAfterTransfer(partitionOp.getSourceInst()));
|
|
}
|
|
|
|
void handleTransferNonTransferrable(
|
|
const PartitionOp &partitionOp, Element transferredVal,
|
|
SILDynamicMergedIsolationInfo isolationRegionInfo) const {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " Emitting TransferNonTransferrable Error!\n"
|
|
<< " ID: %%" << transferredVal << "\n"
|
|
<< " Rep: "
|
|
<< *info->getValueMap().getRepresentative(transferredVal)
|
|
<< " Dynamic Isolation Region: ";
|
|
isolationRegionInfo.printForDiagnostics(llvm::dbgs());
|
|
llvm::dbgs() << '\n');
|
|
auto *self = const_cast<DiagnosticEvaluator *>(this);
|
|
auto nonTransferrableValue =
|
|
info->getValueMap().getRepresentative(transferredVal);
|
|
|
|
self->transferredNonTransferrable.emplace_back(
|
|
partitionOp.getSourceOp(), nonTransferrableValue, isolationRegionInfo);
|
|
}
|
|
|
|
void handleInOutSendingNotDisconnectedAtExitError(
|
|
const PartitionOp &partitionOp, Element inoutSendingVal,
|
|
SILDynamicMergedIsolationInfo isolationRegionInfo) const {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " Emitting InOut Sending ActorIsolated at end of "
|
|
"Function Error!\n"
|
|
<< " ID: %%" << inoutSendingVal << "\n"
|
|
<< " Rep: "
|
|
<< *info->getValueMap().getRepresentative(inoutSendingVal)
|
|
<< " Dynamic Isolation Region: ";
|
|
isolationRegionInfo.printForDiagnostics(llvm::dbgs());
|
|
llvm::dbgs() << '\n');
|
|
auto *self = const_cast<DiagnosticEvaluator *>(this);
|
|
auto nonTransferrableValue =
|
|
info->getValueMap().getRepresentative(inoutSendingVal);
|
|
|
|
self->inoutSendingNotDisconnectedInfoList.emplace_back(
|
|
partitionOp.getSourceInst(), nonTransferrableValue,
|
|
isolationRegionInfo);
|
|
}
|
|
|
|
void handleTransferNonTransferrable(
|
|
const PartitionOp &partitionOp, Element transferredVal,
|
|
Element actualNonTransferrableValue,
|
|
SILDynamicMergedIsolationInfo isolationRegionInfo) const {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " Emitting TransferNonTransferrable Error!\n"
|
|
<< " ID: %%" << transferredVal << "\n"
|
|
<< " Rep: "
|
|
<< *info->getValueMap().getRepresentative(transferredVal)
|
|
<< " Dynamic Isolation Region: ";
|
|
isolationRegionInfo.printForDiagnostics(llvm::dbgs());
|
|
llvm::dbgs() << '\n');
|
|
|
|
auto *self = const_cast<DiagnosticEvaluator *>(this);
|
|
// If we have a non-actor introducing fake representative value, just use
|
|
// the value that actually introduced the actor isolation.
|
|
if (auto nonTransferrableValue = info->getValueMap().maybeGetRepresentative(
|
|
actualNonTransferrableValue)) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " ActualTransfer: " << nonTransferrableValue);
|
|
self->transferredNonTransferrable.emplace_back(partitionOp.getSourceOp(),
|
|
nonTransferrableValue,
|
|
isolationRegionInfo);
|
|
} else if (auto *nonTransferrableInst =
|
|
info->getValueMap().maybeGetActorIntroducingInst(
|
|
actualNonTransferrableValue)) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " ActualTransfer: " << *nonTransferrableInst);
|
|
self->transferredNonTransferrable.emplace_back(
|
|
partitionOp.getSourceOp(), nonTransferrableInst, isolationRegionInfo);
|
|
} else {
|
|
// Otherwise, just use the actual value.
|
|
//
|
|
// TODO: We are eventually going to want to be able to say that it is b/c
|
|
// of the actor isolated parameter. Maybe we should put in the actual
|
|
// region isolation info here.
|
|
self->transferredNonTransferrable.emplace_back(
|
|
partitionOp.getSourceOp(),
|
|
info->getValueMap().getRepresentative(transferredVal),
|
|
isolationRegionInfo);
|
|
}
|
|
}
|
|
|
|
void
|
|
handleInOutSendingNotInitializedAtExitError(const PartitionOp &partitionOp,
|
|
Element inoutSendingVal,
|
|
Operand *transferringOp) const {
|
|
auto rep = info->getValueMap().getRepresentative(inoutSendingVal);
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " Emitting InOut Not Reinitialized At End Of Function!\n"
|
|
<< " Transferring Inst: " << *transferringOp->getUser()
|
|
<< " Transferring Op Value: " << transferringOp->get()
|
|
<< " Require Inst: " << *partitionOp.getSourceInst()
|
|
<< " ID: %%" << inoutSendingVal << "\n"
|
|
<< " Rep: " << *rep << " Transferring Op Num: "
|
|
<< transferringOp->getOperandNumber() << '\n');
|
|
transferOpToRequireInstMultiMap.insert(
|
|
transferringOp, RequireInst::forInOutReinitializationNeeded(
|
|
partitionOp.getSourceInst()));
|
|
}
|
|
|
|
void handleUnknownCodePattern(const PartitionOp &op) const {
|
|
if (shouldAbortOnUnknownPatternMatchError()) {
|
|
llvm::report_fatal_error(
|
|
"RegionIsolation: Aborting on unknown pattern match error");
|
|
}
|
|
|
|
diagnoseError(op.getSourceInst(),
|
|
diag::regionbasedisolation_unknown_pattern);
|
|
}
|
|
|
|
bool isActorDerived(Element element) const {
|
|
return info->getValueMap().getIsolationRegion(element).isActorIsolated();
|
|
}
|
|
|
|
bool isTaskIsolatedDerived(Element element) const {
|
|
return info->getValueMap().getIsolationRegion(element).isTaskIsolated();
|
|
}
|
|
|
|
SILIsolationInfo::Kind hasSpecialDerivation(Element element) const {
|
|
return info->getValueMap().getIsolationRegion(element).getKind();
|
|
}
|
|
|
|
SILIsolationInfo getIsolationRegionInfo(Element element) const {
|
|
return info->getValueMap().getIsolationRegion(element);
|
|
}
|
|
|
|
std::optional<Element> getElement(SILValue value) const {
|
|
return info->getValueMap().getTrackableValue(value).getID();
|
|
}
|
|
|
|
SILValue getRepresentative(SILValue value) const {
|
|
return info->getValueMap()
|
|
.getTrackableValue(value)
|
|
.getRepresentative()
|
|
.maybeGetValue();
|
|
}
|
|
|
|
RepresentativeValue getRepresentativeValue(Element element) const {
|
|
return info->getValueMap().getRepresentativeValue(element);
|
|
}
|
|
|
|
bool isClosureCaptured(Element element, Operand *op) const {
|
|
auto value = info->getValueMap().maybeGetRepresentative(element);
|
|
if (!value)
|
|
return false;
|
|
return info->isClosureCaptured(value, op);
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void TransferNonSendableImpl::runDiagnosticEvaluator() {
|
|
// Then for each block...
|
|
LLVM_DEBUG(llvm::dbgs() << "Walking blocks for diagnostics.\n");
|
|
for (auto [block, blockState] : regionInfo->getRange()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "|--> Block bb" << block.getDebugID() << "\n");
|
|
|
|
if (!blockState.getLiveness()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Dead block... skipping!\n");
|
|
continue;
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Entry Partition: ";
|
|
blockState.getEntryPartition().print(llvm::dbgs()));
|
|
|
|
// Grab its entry partition and setup an evaluator for the partition that
|
|
// has callbacks that emit diagnsotics...
|
|
Partition workingPartition = blockState.getEntryPartition();
|
|
DiagnosticEvaluator eval(workingPartition, regionInfo,
|
|
transferOpToRequireInstMultiMap,
|
|
transferredNonTransferrableInfoList,
|
|
inoutSendingNotDisconnectedInfoList,
|
|
regionInfo->getTransferringOpToStateMap());
|
|
|
|
// And then evaluate all of our partition ops on the entry partition.
|
|
for (auto &partitionOp : blockState.getPartitionOps()) {
|
|
eval.apply(partitionOp);
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Exit Partition: ";
|
|
workingPartition.print(llvm::dbgs()));
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Finished walking blocks for diagnostics.\n");
|
|
|
|
// Now that we have found all of our transferInsts/Requires emit errors.
|
|
transferOpToRequireInstMultiMap.setFrozen();
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Top Level Entrypoint
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Once we have reached a fixpoint, this routine runs over all blocks again
|
|
/// reporting any failures by applying our ops to the converged dataflow
|
|
/// state.
|
|
void TransferNonSendableImpl::emitDiagnostics() {
|
|
auto *function = regionInfo->getFunction();
|
|
LLVM_DEBUG(llvm::dbgs() << "Emitting diagnostics for function "
|
|
<< function->getName() << "\n");
|
|
|
|
runDiagnosticEvaluator();
|
|
emitTransferredNonTransferrableDiagnostics();
|
|
emitUseAfterTransferDiagnostics();
|
|
emitInOutSendingNotDisconnectedInfoList();
|
|
}
|
|
|
|
namespace {
|
|
|
|
class TransferNonSendable : public SILFunctionTransform {
|
|
void run() override {
|
|
SILFunction *function = getFunction();
|
|
|
|
auto *functionInfo = getAnalysis<RegionAnalysis>()->get(function);
|
|
if (!functionInfo->isSupportedFunction()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "===> SKIPPING UNSUPPORTED FUNCTION: "
|
|
<< function->getName() << '\n');
|
|
|
|
return;
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "===> PROCESSING: " << function->getName() << '\n');
|
|
|
|
TransferNonSendableImpl impl(functionInfo);
|
|
impl.emitDiagnostics();
|
|
}
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
SILTransform *swift::createTransferNonSendable() {
|
|
return new TransferNonSendable();
|
|
}
|