Files
swift-mirror/lib/SILOptimizer/Mandatory/TransferNonSendable.cpp
Michael Gottesman ace94b00ba [region-isolation] Move RepresentativeValue from RegionAnalysis.h -> PartitionUtils.h and add APIs for mapping an ElementID -> Representative.
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.
2024-07-18 21:28:22 -07:00

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();
}