Files
swift-mirror/lib/SILOptimizer/Mandatory/SendNonSendable.cpp
Michael Gottesman 22d182552c Merge pull request #85999 from gottesmm/pr-c29e9002447c10b77a408ede1f7b7e42c5034dbf
[rbi] When looking for closure uses, handle unresolved_non_copyable_value and store_borrow temporaries.
2025-12-12 00:09:08 -08:00

3803 lines
137 KiB
C++

//===--- SendNonSendable.cpp ----------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 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 "send-non-sendable"
#include "swift/SIL/PrunedLiveness.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/Concurrency.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 SendingOperandSetFactory = Partition::SendingOperandSetFactory;
using Element = PartitionPrimitives::Element;
using Region = PartitionPrimitives::Region;
} // namespace
// This option is used so we can test typed errors. Typed errors are a fallback
// case which are emitted when we are unable to infer the name of a value. We in
// most cases do succeed inferring, so it makes sense to add an asserts only
// option that can be used by the compiler to test that we emit these correctly.
static llvm::cl::opt<bool> ForceTypedErrors(
"sil-regionbasedisolation-force-use-of-typed-errors",
llvm::cl::desc("Force the usage of typed instead of named errors to make "
"it easier to test typed errors"),
llvm::cl::Hidden);
//===----------------------------------------------------------------------===//
// MARK: Utilities
//===----------------------------------------------------------------------===//
static SILValue stripFunctionConversions(SILValue val) {
while (true) {
if (auto ti = dyn_cast<ThinToThickFunctionInst>(val)) {
val = ti->getOperand();
continue;
}
if (auto cfi = dyn_cast<ConvertFunctionInst>(val)) {
val = cfi->getOperand();
continue;
}
if (auto cvt = dyn_cast<ConvertEscapeToNoEscapeInst>(val)) {
val = cvt->getOperand();
continue;
}
// Look through thunks.
if (auto pai = dyn_cast<PartialApplyInst>(val)) {
if (pai->getCalleeFunction()->isThunk()) {
val = pai->getArgument(0);
continue;
}
}
break;
}
return val;
}
/// Find the most conservative diagnostic behavior by taking the max over all
/// DiagnosticBehavior for the captured values.
static std::optional<DiagnosticBehavior>
getDiagnosticBehaviorLimitForOperands(SILFunction *fn,
ArrayRef<Operand *> capturedValues) {
std::optional<DiagnosticBehavior> diagnosticBehavior;
for (auto value : capturedValues) {
auto lhs = diagnosticBehavior.value_or(DiagnosticBehavior::Unspecified);
auto limit = value->get()->getType().getConcurrencyDiagnosticBehavior(fn);
auto rhs = limit.value_or(DiagnosticBehavior::Unspecified);
auto result = lhs.merge(rhs);
if (result != DiagnosticBehavior::Unspecified)
diagnosticBehavior = result;
}
return diagnosticBehavior;
}
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<ValueDecl *> getSendingApplyCallee(SILInstruction *inst) {
auto declRef = getDeclRefForCallee(inst);
if (!declRef)
return {};
auto *decl = declRef->getDecl();
if (!decl)
return {};
return decl;
}
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;
}
/// Attempt to infer a name for \p value. Returns none if we fail or if we are
/// asked to force typed errors since we are testing.
static std::optional<Identifier> inferNameHelper(SILValue value) {
if (ForceTypedErrors)
return {};
return VariableNameInferrer::inferName(value);
}
/// Attempt to infer a name and root for \p value. Returns none if we fail or if
/// we are asked to force typed errors since we are testing.
static std::optional<std::pair<Identifier, SILValue>>
inferNameAndRootHelper(SILValue value) {
if (ForceTypedErrors)
return {};
return VariableNameInferrer::inferNameAndRoot(value);
}
/// Sometimes we use a store_borrow + temporary to materialize a borrowed value
/// to be passed to another function. We want to emit the error on the function
/// itself, not the store_borrow so we get the best location. We only do this if
/// we can prove that we have a store_borrow to an alloc_stack, the store_borrow
/// is the only thing stored into the alloc_stack, and except for the
/// store_borrow, the alloc_stack has a single non_destroy user, a function we
/// are calling.
static Operand *
findClosureUseThroughStoreBorrowTemporary(StoreBorrowInst *sbi) {
auto *asi = dyn_cast<AllocStackInst>(sbi->getDest());
if (!asi)
return {};
Operand *resultUse = nullptr;
for (auto *use : asi->getUses()) {
if (use == &sbi->getAllOperands()[StoreBorrowInst::Dest])
continue;
if (isa<EndBorrowInst>(use->getUser()))
continue;
auto fas = FullApplySite::isa(use->getUser());
if (!fas)
return {};
resultUse = use;
}
return resultUse;
}
/// Find a use corresponding to the potentially recursive capture of \p
/// initialOperand that would be appropriate for diagnostics.
///
/// \returns the use and the function argument that is used. We return the
/// function argument since it is a clever way to correctly grab the name of the
/// captured value since the ValueDecl will point at the actual ValueDecl in the
/// AST that is captured.
static std::optional<std::pair<Operand *, SILArgument *>>
findClosureUse(Operand *initialOperand) {
// We have to use a small vector worklist here since we are iterating through
// uses from different functions.
llvm::SmallVector<std::pair<Operand *, SILArgument *>, 64> worklist;
llvm::SmallPtrSet<Operand *, 8> visitedOperand;
// Initialize our worklist with uses in the initial closure. We do not want to
// analyze uses in the original function.
{
auto as = ApplySite::isa(initialOperand->getUser());
if (!as)
return {};
auto *f = as.getCalleeFunction();
if (!f || f->empty())
return {};
unsigned argumentIndex = as.getCalleeArgIndex(*initialOperand);
auto *arg = f->getArgument(argumentIndex);
for (auto *use : arg->getUses()) {
worklist.emplace_back(use, arg);
visitedOperand.insert(use);
}
}
while (!worklist.empty()) {
auto pair = worklist.pop_back_val();
auto *op = pair.first;
auto *fArg = pair.second;
auto *user = op->getUser();
// Ignore incidental uses that are not specifically ignored use. We want to
// visit those since they represent `let _ = $VAR` and `_ = $VAR`
if (isIncidentalUse(user) && !isa<IgnoredUseInst>(user))
continue;
// Ignore some instructions we do not care about.
if (isa<DestroyValueInst, EndBorrowInst, EndAccessInst, DeallocStackInst>(
user))
continue;
// Look through some insts we do not care about.
if (isa<CopyValueInst, BeginBorrowInst, ProjectBoxInst, BeginAccessInst>(
user) ||
isa<MarkUnresolvedNonCopyableValueInst>(user) ||
isMoveOnlyWrapperUse(user) ||
// We want to treat move_value [var_decl] as a real use since we are
// assigning to a var.
(isa<MoveValueInst>(user) &&
!cast<MoveValueInst>(user)->isFromVarDecl())) {
for (auto result : user->getResults()) {
for (auto *use : result->getUses()) {
if (visitedOperand.insert(use).second)
worklist.emplace_back(use, fArg);
}
}
continue;
}
// See if we have a callee function. In such a case, find our operand in the
// callee and visit its uses.
if (auto as = dyn_cast<PartialApplyInst>(op->getUser())) {
if (auto *f = as->getCalleeFunction(); f && !f->empty()) {
auto *fArg = f->getArgument(ApplySite(as).getCalleeArgIndex(*op));
for (auto *use : fArg->getUses()) {
if (visitedOperand.insert(use).second)
worklist.emplace_back(use, fArg);
}
continue;
}
}
// See if we have a full apply site that was from a closure that was
// immediately invoked. In such a case, we can emit a better diagnostic in
// the called closure.
if (auto fas = FullApplySite::isa(op->getUser())) {
if (auto *f = fas.getCalleeFunction(); f && !f->empty()) {
auto *fArg = cast<SILFunctionArgument>(
f->getArgument(fas.getCalleeArgIndex(*op)));
if (fArg->isClosureCapture()) {
for (auto *use : fArg->getUses()) {
if (visitedOperand.insert(use).second)
worklist.emplace_back(use, fArg);
}
continue;
}
}
}
// If we have a store_borrow, see if we are using it to just marshal a use
// to a full apply site.
if (auto *sbi = dyn_cast<StoreBorrowInst>(op->getUser());
sbi && op == &sbi->getAllOperands()[StoreBorrowInst::Src]) {
if (auto *use = findClosureUseThroughStoreBorrowTemporary(sbi)) {
if (visitedOperand.insert(use).second)
worklist.emplace_back(use, fArg);
continue;
}
}
// Otherwise, we have a real use. Return it and the function argument that
// it was derived from.
return pair;
}
return {};
}
//===----------------------------------------------------------------------===//
// 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)...)
.warnUntilLanguageMode(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
/// sending instruction. We discover this by walking from user blocks to
struct RequireLiveness {
unsigned generation;
SILInstruction *sendingInst;
BasicBlockData<BlockLivenessInfo> &blockLivenessInfo;
InstructionSet allRequires;
InstructionSetWithSize finalRequires;
/// If we have requires in the def block before our send, this is the
/// first require.
SILInstruction *firstRequireBeforeSendInDefBlock = nullptr;
RequireLiveness(unsigned generation, Operand *sendingOp,
BasicBlockData<BlockLivenessInfo> &blockLivenessInfo)
: generation(generation), sendingInst(sendingOp->getUser()),
blockLivenessInfo(blockLivenessInfo),
allRequires(sendingOp->getParentFunction()),
finalRequires(sendingOp->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 seningInst 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() {
REGIONBASEDISOLATION_LOG(llvm::dbgs() << " Processing def block!\n");
// First walk from the beginning of the block to the send 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 send.
for (auto ii = sendingInst->getParent()->begin(),
ie = sendingInst->getIterator();
ii != ie; ++ii) {
if (allRequires.contains(&*ii) && !firstRequireBeforeSendInDefBlock) {
firstRequireBeforeSendInDefBlock = &*ii;
REGIONBASEDISOLATION_LOG(llvm::dbgs()
<< " Found send before def: "
<< *firstRequireBeforeSendInDefBlock);
break;
}
}
// Then walk from our sendingInst to the end of the block looking for the
// first require inst. Once we find it... return.
//
// NOTE: We start walking at the sendingInst since the sendingInst could use
// the requireInst as well.
for (auto ii = sendingInst->getIterator(),
ie = sendingInst->getParent()->end();
ii != ie; ++ii) {
if (!allRequires.contains(&*ii))
continue;
finalRequires.insert(&*ii);
REGIONBASEDISOLATION_LOG(llvm::dbgs()
<< " Found send 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) {
REGIONBASEDISOLATION_LOG(
llvm::dbgs() << "==> Performing Require Liveness for: " << *sendingInst);
// Then put all of our requires into our allRequires set.
BasicBlockWorklist initializingWorklist(sendingInst->getFunction());
for (auto require : requireInstList) {
REGIONBASEDISOLATION_LOG(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 sendingInst...
processDefBlock();
// If we found /any/ requries after the sendingInst, we can bail early since
// that is guaranteed to dominate all further requires.
if (!finalRequires.empty()) {
REGIONBASEDISOLATION_LOG(
llvm::dbgs()
<< " Found send after def in def block! Exiting early!\n");
return;
}
REGIONBASEDISOLATION_LOG(llvm::dbgs()
<< " Did not find send after def in def "
"block! Walking blocks!\n");
// If we found a send in the def block before our def, add it to the block
// state for the def.
if (firstRequireBeforeSendInDefBlock) {
REGIONBASEDISOLATION_LOG(
llvm::dbgs()
<< " Found a require before send! Adding to block state!\n");
auto blockState = blockLivenessInfo.get(sendingInst->getParent());
blockState.get()->setInst(generation, firstRequireBeforeSendInDefBlock);
}
// Then for each require block that isn't a def block send, find the
// earliest send inst.
while (auto *requireBlock = initializingWorklist.pop()) {
auto blockState = blockLivenessInfo.get(requireBlock);
for (auto &inst : *requireBlock) {
if (!allRequires.contains(&inst))
continue;
REGIONBASEDISOLATION_LOG(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 *sendingBlock = sendingInst->getParent();
BasicBlockWorklist worklist(sendingInst->getFunction());
for (auto *succBlock : sendingBlock->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 sending block.
if (next == sendingBlock)
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 SendNonSendableImpl
//===----------------------------------------------------------------------===//
namespace {
/// 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 send.
class RequireInst {
public:
enum Kind {
UseAfterSend,
InOutReinitializationNeeded,
};
private:
llvm::PointerIntPair<SILInstruction *, 1> instAndKind;
RequireInst(SILInstruction *inst, Kind kind) : instAndKind(inst, kind) {}
public:
static RequireInst forUseAfterSend(SILInstruction *inst) {
return {inst, Kind::UseAfterSend};
}
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 SendNonSendableImpl {
RegionAnalysisFunctionInfo *info;
SmallFrozenMultiMap<Operand *, RequireInst, 8> sendingOpToRequireInstMultiMap;
SmallVector<PartitionOpError, 8> foundVerbatimErrors;
public:
SendNonSendableImpl(RegionAnalysisFunctionInfo *info) : info(info) {}
void emitDiagnostics();
private:
void runDiagnosticEvaluator();
void emitUseAfterSendDiagnostics();
void emitVerbatimErrors();
};
} // namespace
//===----------------------------------------------------------------------===//
// MARK: Diagnostic Evaluator
//===----------------------------------------------------------------------===//
namespace {
struct DiagnosticEvaluator final
: PartitionOpEvaluatorBaseImpl<DiagnosticEvaluator> {
RegionAnalysisFunctionInfo *info;
SmallFrozenMultiMap<Operand *, RequireInst, 8>
&sendingOpToRequireInstMultiMap;
/// An error that we know how to emit verbatim without needing to preprocess.
///
/// A contrasting case here is the use after send error where we need to pair
/// sending operands to require insts.
SmallVectorImpl<PartitionOpError> &foundVerbatimErrors;
DiagnosticEvaluator(Partition &workingPartition,
RegionAnalysisFunctionInfo *info,
SmallFrozenMultiMap<Operand *, RequireInst, 8>
&sendingOpToRequireInstMultiMap,
SmallVectorImpl<PartitionOpError> &foundVerbatimErrors,
SendingOperandToStateMap &operandToStateMap)
: PartitionOpEvaluatorBaseImpl(
workingPartition, info->getOperandSetFactory(), operandToStateMap),
info(info),
sendingOpToRequireInstMultiMap(sendingOpToRequireInstMultiMap),
foundVerbatimErrors(foundVerbatimErrors) {}
void handleLocalUseAfterSend(LocalUseAfterSendError &&error) const {
const auto &partitionOp = *error.op;
REGIONBASEDISOLATION_LOG(error.print(llvm::dbgs(), info->getValueMap()));
// Ignore this if we are erroring on a mutable base of a Sendable value and
// if when we sent the value's region was not closure captured.
if (error.op->getOptions().containsOnly(
PartitionOp::Flag::RequireOfMutableBaseOfSendableValue) &&
!operandToStateMap.get(error.sendingOp).isClosureCaptured)
return;
sendingOpToRequireInstMultiMap.insert(
error.sendingOp, RequireInst::forUseAfterSend(partitionOp.getSourceInst()));
}
void handleInOutSendingNotInitializedAtExitError(
InOutSendingNotInitializedAtExitError &&error) const {
const PartitionOp &partitionOp = *error.op;
Operand *sendingOp = error.sendingOp;
REGIONBASEDISOLATION_LOG(error.print(llvm::dbgs(), info->getValueMap()));
sendingOpToRequireInstMultiMap.insert(
sendingOp, RequireInst::forInOutReinitializationNeeded(
partitionOp.getSourceInst()));
}
void handleUnknownCodePattern(UnknownCodePatternError &&error) const {
const PartitionOp &op = *error.op;
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(op.getSourceInst(),
diag::regionbasedisolation_unknown_pattern);
}
void handleError(PartitionOpError &&error) {
switch (error.getKind()) {
case PartitionOpError::LocalUseAfterSend: {
return handleLocalUseAfterSend(
std::move(error).getLocalUseAfterSendError());
}
case PartitionOpError::InOutSendingNotDisconnectedAtExit:
case PartitionOpError::InOutSendingReturned:
case PartitionOpError::SentNeverSendable:
case PartitionOpError::AssignNeverSendableIntoSendingResult:
case PartitionOpError::NonSendableIsolationCrossingResult:
case PartitionOpError::InOutSendingParametersInSameRegion:
// We are going to process these later... but dump so we can see that we
// handled an error here. The rest of the explicit handlers will dump as
// appropriate if they want to emit an error here (some will squelch the
// error).
REGIONBASEDISOLATION_LOG(error.print(llvm::dbgs(), info->getValueMap()));
foundVerbatimErrors.emplace_back(std::move(error));
return;
case PartitionOpError::InOutSendingNotInitializedAtExit: {
return handleInOutSendingNotInitializedAtExitError(
std::move(error).getInOutSendingNotInitializedAtExitError());
}
case PartitionOpError::UnknownCodePattern: {
return handleUnknownCodePattern(
std::move(error).getUnknownCodePatternError());
}
}
llvm_unreachable("Covered switch isn't covered?!");
}
bool isActorDerived(Element element) const {
return info->getValueMap().getIsolationRegion(element).isActorIsolated();
}
/// If \p element's representative is an indirect out parameter, return
/// that parameter.
SILValue getIndirectOutParameter(Element element) const {
auto rep = info->getValueMap().getRepresentativeValue(element);
if (!rep)
return {};
if (auto value = dyn_cast_or_null<SILFunctionArgument>(rep.maybeGetValue());
value && value->getArgumentConvention().isIndirectOutParameter())
return value;
return {};
}
SILValue getInOutSendingParameter(Element elt) const {
auto rep = info->getValueMap().getRepresentativeValue(elt);
if (!rep)
return {};
if (auto value = dyn_cast_or_null<SILFunctionArgument>(rep.maybeGetValue());
value && value->getArgumentConvention().isInoutConvention() &&
value->isSending())
return value;
return {};
}
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);
}
/// Only return an element if we are already tracking value and it is
/// non-Sendable.
///
/// TODO: Can we return only
std::optional<Element> getElement(SILValue value) const {
auto trackableValue = info->getValueMap().getTrackableValue(value);
if (trackableValue.value.isSendable())
return {};
return trackableValue.value.getID();
}
SILValue getRepresentative(SILValue value) const {
return info->getValueMap()
.getTrackableValue(value)
.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 SendNonSendableImpl::runDiagnosticEvaluator() {
// Then for each block...
REGIONBASEDISOLATION_LOG(llvm::dbgs() << "Walking blocks for diagnostics.\n");
for (auto [block, blockState] : info->getRange()) {
REGIONBASEDISOLATION_LOG(llvm::dbgs()
<< "|--> Block bb" << block.getDebugID() << "\n");
if (!blockState.getLiveness()) {
REGIONBASEDISOLATION_LOG(llvm::dbgs() << "Dead block... skipping!\n");
continue;
}
REGIONBASEDISOLATION_LOG(
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, info, sendingOpToRequireInstMultiMap,
foundVerbatimErrors, info->getSendingOperandToStateMap());
// And then evaluate all of our partition ops on the entry partition.
for (auto &partitionOp : blockState.getPartitionOps()) {
eval.apply(partitionOp);
}
REGIONBASEDISOLATION_LOG(llvm::dbgs() << "Exit Partition: ";
workingPartition.print(llvm::dbgs()));
}
REGIONBASEDISOLATION_LOG(llvm::dbgs()
<< "Finished walking blocks for diagnostics.\n");
// Now that we have found all of our sendingInsts/Requires emit errors.
sendingOpToRequireInstMultiMap.setFrozen();
}
//===----------------------------------------------------------------------===//
// MARK: UseAfterSend Diagnostic Inference
//===----------------------------------------------------------------------===//
namespace {
class UseAfterSendDiagnosticEmitter {
Operand *sendingOp;
SmallVectorImpl<RequireInst> &requireInsts;
bool emittedErrorDiagnostic = false;
public:
UseAfterSendDiagnosticEmitter(Operand *sendingOp,
SmallVectorImpl<RequireInst> &requireInsts)
: sendingOp(sendingOp), requireInsts(requireInsts) {}
~UseAfterSendDiagnosticEmitter() {
// If we were supposed to emit a diagnostic and didn't emit an unknown
// pattern error.
if (!emittedErrorDiagnostic)
emitUnknownPatternError();
}
SILFunction *getFunction() const { return sendingOp->getFunction(); }
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
return sendingOp->get()->getType().getConcurrencyDiagnosticBehavior(
getFunction());
}
/// Attempts to retrieve and return the callee declaration.
std::optional<const ValueDecl *> getSendingCallee() const {
return getSendingApplyCallee(sendingOp->getUser());
}
void
emitNamedIsolationCrossingError(SILLocation loc, Identifier name,
SILIsolationInfo namedValuesIsolationInfo,
ApplyIsolationCrossing isolationCrossing) {
// Emit the short error.
diagnoseError(loc, diag::regionbasedisolation_named_send_yields_race, name)
.limitBehaviorIf(getBehaviorLimit());
// Then emit the note with greater context.
auto descriptiveKindStr =
namedValuesIsolationInfo.printForDiagnostics(getFunction());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCalleeIsolation());
auto callerIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCallerIsolation());
bool isDisconnected = namedValuesIsolationInfo.isDisconnected();
if (auto callee = getSendingCallee()) {
diagnoseNote(
loc, diag::regionbasedisolation_named_info_send_yields_race_callee,
isDisconnected, name, descriptiveKindStr, calleeIsolationStr,
callee.value(), callerIsolationStr);
} else {
diagnoseNote(loc, diag::regionbasedisolation_named_info_send_yields_race,
isDisconnected, name, descriptiveKindStr, calleeIsolationStr,
callerIsolationStr);
}
emitRequireInstDiagnostics();
}
void
emitNamedIsolationCrossingError(SILLocation loc, Identifier name,
SILIsolationInfo namedValuesIsolationInfo,
ApplyIsolationCrossing isolationCrossing,
const ValueDecl *callee) {
// Emit the short error.
diagnoseError(loc, diag::regionbasedisolation_named_send_yields_race, name)
.limitBehaviorIf(getBehaviorLimit());
// Then emit the note with greater context.
auto descriptiveKindStr =
namedValuesIsolationInfo.printForDiagnostics(getFunction());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCalleeIsolation());
auto callerIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCallerIsolation());
bool isDisconnected = namedValuesIsolationInfo.isDisconnected();
diagnoseNote(loc,
diag::regionbasedisolation_named_info_send_yields_race_callee,
isDisconnected, name, descriptiveKindStr, calleeIsolationStr,
callee, callerIsolationStr);
emitRequireInstDiagnostics();
}
void emitNamedAsyncLetNoIsolationCrossingError(SILLocation loc,
Identifier name) {
// Emit the short error.
diagnoseError(loc, diag::regionbasedisolation_named_send_yields_race, name)
.limitBehaviorIf(getBehaviorLimit());
diagnoseNote(
loc, diag::regionbasedisolation_named_nonisolated_asynclet_name, name);
emitRequireInstDiagnostics();
}
void emitTypedIsolationCrossing(SILLocation loc, Type inferredType,
ApplyIsolationCrossing isolationCrossing) {
diagnoseError(loc, diag::regionbasedisolation_type_send_yields_race,
inferredType)
.limitBehaviorIf(getBehaviorLimit());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCalleeIsolation());
auto callerIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCallerIsolation());
if (auto callee = getSendingCallee()) {
diagnoseNote(loc, diag::regionbasedisolation_type_use_after_send_callee,
inferredType, calleeIsolationStr, callee.value(),
callerIsolationStr);
} else {
diagnoseNote(loc, diag::regionbasedisolation_type_use_after_send,
inferredType, calleeIsolationStr, callerIsolationStr);
}
emitRequireInstDiagnostics();
}
void emitNamedUseofStronglySentValue(SILLocation loc, Identifier name) {
// Emit the short error.
diagnoseError(loc, diag::regionbasedisolation_named_send_yields_race, name)
.limitBehaviorIf(getBehaviorLimit());
// Then emit the note with greater context.
diagnoseNote(
loc, diag::regionbasedisolation_named_value_used_after_explicit_sending,
name);
// Finally the require points.
emitRequireInstDiagnostics();
}
void emitTypedUseOfStronglySentValue(SILLocation loc, Type inferredType) {
diagnoseError(loc, diag::regionbasedisolation_type_send_yields_race,
inferredType)
.limitBehaviorIf(getBehaviorLimit());
if (auto callee = getSendingCallee()) {
diagnoseNote(loc,
diag::regionbasedisolation_typed_use_after_sending_callee,
inferredType, callee.value());
} else {
diagnoseNote(loc, diag::regionbasedisolation_typed_use_after_sending,
inferredType);
}
emitRequireInstDiagnostics();
}
void emitNamedIsolationCrossingDueToCapture(
SILLocation loc, Identifier name,
SILIsolationInfo namedValuesIsolationInfo,
ApplyIsolationCrossing isolationCrossing) {
// Emit the short error.
diagnoseError(loc, diag::regionbasedisolation_named_send_yields_race, name)
.limitBehaviorIf(getBehaviorLimit());
auto descriptiveKindStr =
namedValuesIsolationInfo.printForDiagnostics(getFunction());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCalleeIsolation());
auto callerIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCallerIsolation());
bool isDisconnected = namedValuesIsolationInfo.isDisconnected();
diagnoseNote(loc,
diag::regionbasedisolation_named_isolated_closure_yields_race,
isDisconnected, descriptiveKindStr, name, calleeIsolationStr,
callerIsolationStr);
emitRequireInstDiagnostics();
}
void emitTypedIsolationCrossingDueToCapture(
SILLocation loc, Type inferredType,
ApplyIsolationCrossing isolationCrossing) {
diagnoseError(loc, diag::regionbasedisolation_type_send_yields_race,
inferredType)
.limitBehaviorIf(getBehaviorLimit());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCalleeIsolation());
auto callerIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCallerIsolation());
diagnoseNote(loc,
diag::regionbasedisolation_type_isolated_capture_yields_race,
inferredType, calleeIsolationStr, callerIsolationStr);
emitRequireInstDiagnostics();
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(sendingOp->getUser(),
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getBehaviorLimit());
}
private:
ASTContext &getASTContext() const {
return sendingOp->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)...)
.warnUntilLanguageMode(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::UseAfterSend:
diagnoseNote(*require, diag::regionbasedisolation_maybe_race);
continue;
case RequireInst::InOutReinitializationNeeded:
diagnoseNote(
*require,
diag::regionbasedisolation_inout_sending_must_be_reinitialized);
continue;
}
llvm_unreachable("Covered switch isn't covered?!");
}
}
};
class UseAfterSendDiagnosticInferrer {
Operand *sendingOp;
UseAfterSendDiagnosticEmitter diagnosticEmitter;
RegionAnalysisValueMap &valueMap;
SendingOperandToStateMap &sendingOpToStateMap;
SILLocation baseLoc = SILLocation::invalid();
Type baseInferredType;
struct AutoClosureWalker;
public:
UseAfterSendDiagnosticInferrer(Operand *sendOp,
SmallVectorImpl<RequireInst> &requireInsts,
RegionAnalysisValueMap &valueMap,
SendingOperandToStateMap &sendingOpToStateMap)
: sendingOp(sendOp), diagnosticEmitter(sendOp, requireInsts),
valueMap(valueMap), sendingOpToStateMap(sendingOpToStateMap),
baseLoc(sendOp->getUser()->getLoc()),
baseInferredType(sendOp->get()->getType().getASTType()) {}
void infer();
Operand *getSendingOperand() const { return sendingOp; }
private:
bool initForIsolatedPartialApply(Operand *op, AbstractClosureExpr *ace);
void initForApply(Operand *op, ApplyExpr *expr);
void initForAutoclosure(Operand *op, AutoClosureExpr *expr);
};
} // namespace
bool UseAfterSendDiagnosticInferrer::initForIsolatedPartialApply(
Operand *op, AbstractClosureExpr *ace) {
auto diagnosticPair = findClosureUse(op);
if (!diagnosticPair) {
return false;
}
auto *diagnosticOp = diagnosticPair->first;
ApplyIsolationCrossing crossing(
*op->getFunction()->getActorIsolation(),
*diagnosticOp->getFunction()->getActorIsolation());
auto &state = sendingOpToStateMap.get(sendingOp);
if (auto rootValueAndName = inferNameAndRootHelper(sendingOp->get())) {
diagnosticEmitter.emitNamedIsolationCrossingDueToCapture(
diagnosticOp->getUser()->getLoc(), rootValueAndName->first,
state.isolationInfo.getIsolationInfo(), crossing);
return true;
}
diagnosticEmitter.emitTypedIsolationCrossingDueToCapture(
diagnosticOp->getUser()->getLoc(), baseInferredType, crossing);
return true;
}
void UseAfterSendDiagnosticInferrer::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 sent");
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 UseAfterSendDiagnosticInferrer::AutoClosureWalker : ASTWalker {
UseAfterSendDiagnosticInferrer &foundTypeInfo;
ValueDecl *targetDecl;
SILIsolationInfo targetDeclIsolationInfo;
SmallPtrSet<Expr *, 8> visitedCallExprDeclRefExprs;
AutoClosureWalker(UseAfterSendDiagnosticInferrer &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 sent 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);
continue;
}
// Otherwise default back to the "callee" error.
foundTypeInfo.diagnosticEmitter.emitNamedIsolationCrossingError(
foundTypeInfo.baseLoc, targetDecl->getBaseIdentifier(),
targetDeclIsolationInfo, *isolationCrossing);
continue;
}
}
}
return Action::Continue(expr);
}
};
void UseAfterSendDiagnosticInferrer::infer() {
// Otherwise, see if our operand's instruction is a sending parameter.
if (auto fas = FullApplySite::isa(sendingOp->getUser())) {
assert(!fas.getArgumentConvention(*sendingOp).isIndirectOutParameter() &&
"We should never send an indirect out parameter");
if (fas.getArgumentParameterInfo(*sendingOp)
.hasOption(SILParameterInfo::Sending)) {
// First try to do the named diagnostic if we can find a name.
if (auto rootValueAndName = inferNameAndRootHelper(sendingOp->get())) {
return diagnosticEmitter.emitNamedUseofStronglySentValue(
baseLoc, rootValueAndName->first);
}
// See if we have an ApplyExpr and if we can infer a better type.
Type type = baseInferredType;
if (auto *applyExpr =
sendingOp->getUser()->getLoc().getAsASTNode<ApplyExpr>()) {
if (auto *foundExpr =
inferArgumentExprFromApplyExpr(applyExpr, fas, sendingOp))
type = foundExpr->findOriginalType();
}
// Otherwise, emit the typed diagnostic.
return diagnosticEmitter.emitTypedUseOfStronglySentValue(baseLoc, type);
}
}
auto loc = sendingOp->getUser()->getLoc();
// If we have a partial_apply that is actor isolated, see if we found a
// send error due to us sending a value into the partial apply.
if (auto *ace = loc.getAsASTNode<AbstractClosureExpr>()) {
if (ace->getActorIsolation().isActorIsolated()) {
if (initForIsolatedPartialApply(sendingOp, 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 = inferNameAndRootHelper(sendingOp->get())) {
auto &state = sendingOpToStateMap.get(sendingOp);
return diagnosticEmitter.emitNamedIsolationCrossingError(
baseLoc, rootValueAndName->first,
state.isolationInfo.getIsolationInfo(),
*sourceApply->getIsolationCrossing());
}
// Otherwise, try to infer from the ApplyExpr.
return initForApply(sendingOp, sourceApply);
}
if (auto fas = FullApplySite::isa(sendingOp->getUser())) {
if (auto isolationCrossing = fas.getIsolationCrossing()) {
return diagnosticEmitter.emitTypedIsolationCrossing(
baseLoc, baseInferredType, *isolationCrossing);
}
}
auto *autoClosureExpr = loc.getAsASTNode<AutoClosureExpr>();
if (!autoClosureExpr) {
return diagnosticEmitter.emitUnknownPatternError();
}
auto *i = sendingOp->getUser();
auto pai = ApplySite::isa(i);
unsigned captureIndex = pai.getASTAppliedArgIndex(*sendingOp);
auto &state = sendingOpToStateMap.get(sendingOp);
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 send diagnostics.
void SendNonSendableImpl::emitUseAfterSendDiagnostics() {
auto *function = info->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 (sendingOpToRequireInstMultiMap.empty())
return;
REGIONBASEDISOLATION_LOG(
llvm::dbgs() << "Emitting Error. Kind: Use After Send diagnostics.\n");
for (auto [sendingOp, requireInsts] :
sendingOpToRequireInstMultiMap.getRange()) {
REGIONBASEDISOLATION_LOG(
llvm::dbgs() << "Sending Op. Number: " << sendingOp->getOperandNumber()
<< ". User: " << *sendingOp->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, sendingOp,
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(sendingOp, diag::regionbasedisolation_unknown_pattern);
continue;
}
UseAfterSendDiagnosticInferrer diagnosticInferrer(
sendingOp, requireInstsForError, info->getValueMap(),
info->getSendingOperandToStateMap());
diagnosticInferrer.infer();
}
}
//===----------------------------------------------------------------------===//
// MARK: Send Never-Sent Diagnostic Inference
//===----------------------------------------------------------------------===//
namespace {
class SendNeverSentDiagnosticEmitter {
/// The use that actually caused the send.
Operand *sendingOperand;
/// The never-sent value that is in the same region as \p
/// sendingOperand.get().
llvm::PointerUnion<SILValue, SILInstruction *> neverSent;
/// The region info that describes the dynamic dataflow derived isolation
/// region info for the never-sent value.
///
/// This is equal to the merge of the IsolationRegionInfo from all elements in
/// the never-sent value's region when the error was diagnosed.
SILDynamicMergedIsolationInfo isolationRegionInfo;
bool emittedErrorDiagnostic = false;
public:
SendNeverSentDiagnosticEmitter(
Operand *sendingOperand,
llvm::PointerUnion<SILValue, SILInstruction *> neverSent,
SILDynamicMergedIsolationInfo isolationRegionInfo)
: sendingOperand(sendingOperand), neverSent(neverSent),
isolationRegionInfo(isolationRegionInfo) {}
~SendNeverSentDiagnosticEmitter() {
if (!emittedErrorDiagnostic) {
emitUnknownPatternError();
} else if (auto proto = isolationRegionInfo.getIsolationInfo()
.getIsolatedConformance()) {
// If the diagnostic comes from a (potentially) isolated conformance,
// add a note saying so and indicating where the isolated conformance
// can come in.
if (auto value = isolationRegionInfo.getIsolationInfo().getIsolatedValue()) {
if (auto loc = value.getLoc()) {
diagnoseNote(
loc, diag::regionbasedisolation_isolated_conformance_introduced,
proto);
}
}
}
}
Operand *getOperand() const { return sendingOperand; }
SILFunction *getFunction() const { return getOperand()->getFunction(); }
SILValue getNeverSentValue() const { return neverSent.dyn_cast<SILValue>(); }
SILInstruction *getNeverSentActorIntroducingInst() const {
return neverSent.dyn_cast<SILInstruction *>();
}
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
// If the failure is due to an isolated conformance, downgrade the error
// to a warning prior to Swift 7.
if (isolationRegionInfo.getIsolationInfo().getIsolatedConformance() &&
!sendingOperand->get()
->getType()
.getASTType()
->getASTContext()
.isLanguageModeAtLeast(
version::Version::getFutureMajorLanguageVersion()))
return DiagnosticBehavior::Warning;
return sendingOperand->get()->getType().getConcurrencyDiagnosticBehavior(
getOperand()->getFunction());
}
/// Attempts to retrieve and return the callee declaration.
std::optional<const ValueDecl *> getSendingCallee() const {
return getSendingApplyCallee(sendingOperand->getUser());
}
SILLocation getLoc() const { return sendingOperand->getUser()->getLoc(); }
/// Return the isolation region info for \p getNeverSentValue().
SILDynamicMergedIsolationInfo getIsolationRegionInfo() const {
return 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_sent)
.limitBehaviorIf(getBehaviorLimit());
}
void emitPassToApply(SILLocation loc, Type inferredType,
ApplyIsolationCrossing crossing) {
diagnoseError(loc, diag::regionbasedisolation_type_send_yields_race,
inferredType)
.limitBehaviorIf(getBehaviorLimit());
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), crossing.getCalleeIsolation());
if (auto callee = getSendingCallee()) {
diagnoseNote(
loc,
diag::regionbasedisolation_typed_sendneversendable_via_arg_callee,
descriptiveKindStr, inferredType, calleeIsolationStr, callee.value());
} else {
diagnoseNote(loc,
diag::regionbasedisolation_typed_sendneversendable_via_arg,
descriptiveKindStr, inferredType, calleeIsolationStr);
}
}
void emitNamedFunctionArgumentClosure(SILLocation loc, Identifier name,
ApplyIsolationCrossing crossing) {
emitNamedOnlyError(loc, name);
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), crossing.getCalleeIsolation());
auto callerIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), crossing.getCallerIsolation());
bool isDisconnected = getIsolationRegionInfo().isDisconnected();
diagnoseNote(loc,
diag::regionbasedisolation_named_isolated_closure_yields_race,
isDisconnected, descriptiveKindStr, name, calleeIsolationStr,
callerIsolationStr);
}
void emitTypedSendingNeverSendableToSendingParam(SILLocation loc,
Type inferredType) {
diagnoseError(loc, diag::regionbasedisolation_type_send_yields_race,
inferredType)
.limitBehaviorIf(getBehaviorLimit());
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
if (auto callee = getSendingCallee()) {
diagnoseNote(
loc, diag::regionbasedisolation_typed_tns_passed_to_sending_callee,
descriptiveKindStr, inferredType, callee.value());
} else {
diagnoseNote(loc, diag::regionbasedisolation_typed_tns_passed_to_sending,
descriptiveKindStr, inferredType);
}
}
/// Emit an error for a case where we have captured a value like an actor and
/// thus a sending closure has become actor isolated (and thus unable to be
/// sent).
void emitClosureErrorWithCapturedActor(Operand *partialApplyOp,
Operand *actualUse,
SILArgument *fArg) {
auto descriptiveKindStr =
getIsolationRegionInfo().printForCodeDiagnostic(getFunction());
diagnoseError(partialApplyOp,
diag::regionbasedisolation_typed_tns_passed_sending_closure,
descriptiveKindStr)
.limitBehaviorIf(getDiagnosticBehaviorLimitForOperands(
actualUse->getFunction(), {actualUse}));
descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
diagnoseNote(actualUse, diag::regionbasedisolation_closure_captures_actor,
fArg->getDecl()->getName(), descriptiveKindStr);
}
/// Emit a typed error for an isolated closure being passed as a sending
/// parameter.
///
/// \arg partialApplyOp the operand of the outermost partial apply.
/// \arg actualUse the operand inside the closure that actually caused the
/// capture to occur. This maybe inside a different function from the partial
/// apply since we want to support a use inside a recursive closure.
void emitSendingClosureParamDirectlyIsolated(Operand *partialApplyOp,
Operand *actualUse,
SILArgument *fArg) {
auto descriptiveKindStr =
getIsolationRegionInfo().printForCodeDiagnostic(getFunction());
diagnoseError(partialApplyOp,
diag::regionbasedisolation_typed_tns_passed_sending_closure,
descriptiveKindStr)
.limitBehaviorIf(getDiagnosticBehaviorLimitForOperands(
actualUse->getFunction(), {actualUse}));
// If we have a closure capture box, emit a special diagnostic.
if (getIsolationRegionInfo().getIsolationInfo().isTaskIsolated()) {
if (cast<SILFunctionArgument>(fArg)->isClosureCapture() &&
fArg->getType().is<SILBoxType>()) {
auto diag = diag::
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_boxed_value_task_isolated;
diagnoseNote(actualUse, diag, fArg->getDecl());
return;
}
auto diag = diag::
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_task_isolated;
diagnoseNote(actualUse, diag, fArg->getDecl()->getName());
return;
}
descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
auto diag = diag::
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value;
diagnoseNote(actualUse, diag, descriptiveKindStr,
fArg->getDecl()->getName());
}
void emitSendingClosureMultipleCapturedOperandError(
SILLocation loc, ArrayRef<Operand *> capturedOperands) {
// Our caller should have passed at least one operand. Emit an unknown error
// to signal we need a bug report.
if (capturedOperands.empty()) {
emitUnknownPatternError();
return;
}
auto descriptiveKindStr =
getIsolationRegionInfo()->printForCodeDiagnostic(getFunction());
auto emitMainError = [&] {
auto behaviorLimit = getDiagnosticBehaviorLimitForOperands(
getFunction(), capturedOperands);
diagnoseError(loc,
diag::regionbasedisolation_typed_tns_passed_sending_closure,
descriptiveKindStr)
.limitBehaviorIf(behaviorLimit);
};
if (capturedOperands.size() == 1) {
auto captured = capturedOperands.front();
auto actualUseInfo = findClosureUse(captured);
// If we fail to find actual use info, emit an unknown error.
if (!actualUseInfo) {
emitUnknownPatternError();
return;
}
if (getIsolationRegionInfo()->isTaskIsolated()) {
emitMainError();
auto diag = diag::
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_task_isolated;
diagnoseNote(actualUseInfo->first, diag,
actualUseInfo->second->getDecl()->getName());
return;
}
emitMainError();
descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
auto diag = diag::
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_region;
diagnoseNote(actualUseInfo->first, diag, descriptiveKindStr,
actualUseInfo->second->getDecl()->getName());
return;
}
emitMainError();
bool emittedDiagnostic = false;
for (auto captured : capturedOperands) {
auto actualUseInfo = findClosureUse(captured);
if (!actualUseInfo)
continue;
emittedDiagnostic = true;
auto diag = diag::
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_multiple_value;
diagnoseNote(actualUseInfo->first, diag,
actualUseInfo->second->getDecl()->getName());
}
// Check if we did not emit a diagnostic. In such a case, we need to emit an
// unknown patten error so that we get a bug report from the user.
if (!emittedDiagnostic) {
emitUnknownPatternError();
}
}
void emitNamedOnlyError(SILLocation loc, Identifier name) {
diagnoseError(loc, diag::regionbasedisolation_named_send_yields_race, name)
.limitBehaviorIf(getBehaviorLimit());
}
void emitNamedAsyncLetCapture(SILLocation loc, Identifier name,
SILIsolationInfo sentValueIsolation) {
assert(!getIsolationRegionInfo().isDisconnected() &&
"Should never be disconnected?!");
emitNamedOnlyError(loc, name);
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
diagnoseNote(loc, diag::regionbasedisolation_named_send_nt_asynclet_capture,
name, descriptiveKindStr);
}
void emitNamedIsolation(SILLocation loc, Identifier name,
ApplyIsolationCrossing isolationCrossing) {
emitNamedOnlyError(loc, name);
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
auto calleeIsolationStr =
SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing.getCalleeIsolation());
bool isDisconnected = getIsolationRegionInfo().isDisconnected();
if (auto callee = getSendingCallee()) {
diagnoseNote(loc,
diag::regionbasedisolation_named_send_never_sendable_callee,
isDisconnected, name, descriptiveKindStr, calleeIsolationStr,
callee.value(), descriptiveKindStr);
} else {
diagnoseNote(loc, diag::regionbasedisolation_named_send_never_sendable,
isDisconnected, name, descriptiveKindStr, calleeIsolationStr,
descriptiveKindStr);
}
}
void emitNamedSendingNeverSendableToSendingParam(SILLocation loc,
Identifier varName) {
emitNamedOnlyError(loc, varName);
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
bool isDisconnected = getIsolationRegionInfo().isDisconnected();
auto diag = diag::regionbasedisolation_named_send_into_sending_param;
diagnoseNote(loc, diag, isDisconnected, descriptiveKindStr, varName);
}
void emitNamedSendingReturn(SILLocation loc, Identifier varName) {
emitNamedOnlyError(loc, varName);
auto descriptiveKindStr =
getIsolationRegionInfo().printForDiagnostics(getFunction());
bool isDisconnected = getIsolationRegionInfo().isDisconnected();
auto diag = diag::regionbasedisolation_named_nosend_send_into_result;
diagnoseNote(loc, diag, isDisconnected, descriptiveKindStr, 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)...)
.warnUntilLanguageMode(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 diagnoseError(Operand *op, Diag<T...> diag, U &&...args) {
return diagnoseError(op->getUser()->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)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(Operand *op, Diag<T...> diag, U &&...args) {
return diagnoseNote(op->getUser()->getLoc(), diag,
std::forward<U>(args)...);
}
};
class SentNeverSendableDiagnosticInferrer {
struct AutoClosureWalker;
RegionAnalysisValueMap &valueMap;
SendNeverSentDiagnosticEmitter diagnosticEmitter;
using SentNeverSendableError = PartitionOpError::SentNeverSendableError;
public:
SentNeverSendableDiagnosticInferrer(RegionAnalysisValueMap &valueMap,
SentNeverSendableError error)
: valueMap(valueMap),
diagnosticEmitter(error.op->getSourceOp(),
valueMap.getRepresentative(error.sentElement),
error.isolationRegionInfo) {}
/// 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 = {});
bool initForSendingPartialApply(FullApplySite fas, Operand *pai);
std::optional<unsigned>
getIsolatedValuePartialApplyIndex(PartialApplyInst *pai,
SILValue isolatedValue) {
for (auto &paiOp : ApplySite(pai).getArgumentOperands()) {
if (valueMap.getTrackableValue(paiOp.get()).value.getRepresentative() ==
isolatedValue) {
return ApplySite(pai).getASTAppliedArgIndex(paiOp);
}
}
return {};
}
};
} // namespace
bool SentNeverSendableDiagnosticInferrer::initForSendingPartialApply(
FullApplySite fas, Operand *callsiteOp) {
// This is the partial apply that is being passed as a sending parameter.
auto *sendingPAI =
dyn_cast<PartialApplyInst>(stripFunctionConversions(callsiteOp->get()));
if (!sendingPAI)
return false;
// Make sure that we only handle closure literals.
//
// TODO: This should be marked on closures at the SIL level... I shouldn't
// have to refer to the AST.
if (!sendingPAI->getLoc().getAsASTNode<ClosureExpr>())
return false;
// Ok, we have a closure literal. First we handle a potential capture of
// 'self' before we do anything by looping over our captured parameters.
//
// DISCUSSION: The reason why we do this early is that as a later heuristic,
// we check if any of the values are directly task isolated (i.e. they are
// actually the task isolated value, not a value that is in the same region as
// something that is task isolated). This could potentially result in us
// emitting a task isolated error instead of an actor isolated error here if
// for some reason SILGen makes the self capture come later in the capture
// list. From a compile time perspective, going over a list of captures twice
// is not going to hurt especially since we are going to emit a diagnostic
// here anyways.
for (auto &sendingPAIOp : sendingPAI->getArgumentOperands()) {
// NOTE: If we access a field on self in the closure, we will still just
// capture self... so we do not have to handle that case due to the way
// SILGen codegens today. This is also true if we use a capture list [x =
// self.field] (i.e. a closure that captures a field from self is still
// nonisolated).
if (!sendingPAIOp.get()->getType().isAnyActor())
continue;
auto *fArg = dyn_cast<SILFunctionArgument>(
lookThroughOwnershipInsts(sendingPAIOp.get()));
if (!fArg || !fArg->isSelf())
continue;
auto capturedValue = findClosureUse(&sendingPAIOp);
if (!capturedValue) {
// If we failed to find the direct capture of self, emit an unknown code
// pattern error so the user knows to send a bug report. This should never
// fail.
diagnosticEmitter.emitUnknownPatternError();
return true;
}
// Otherwise, emit our captured actor error.
diagnosticEmitter.emitClosureErrorWithCapturedActor(
&sendingPAIOp, capturedValue->first, capturedValue->second);
return true;
}
// Ok, we know that we have a closure expr. We now need to find the specific
// closure captured value that is actor or task isolated. Then we search for
// the potentially recursive closure use so we can show a nice loc to the
// user.
auto maybeIsolatedValue =
diagnosticEmitter.getIsolationRegionInfo()->maybeGetIsolatedValue();
// If we do not find an actual task isolated value while looping below, this
// contains the non sendable captures of the partial apply that we want to
// emit a more heuristic based error for. See documentation below.
SmallVector<Operand *, 8> nonSendableOps;
for (auto &sendingPAIOp : sendingPAI->getArgumentOperands()) {
// If our value's rep is task isolated or is the dynamic isolated
// value... then we are done. This is a 'correct' error value to emit.
auto trackableValue = valueMap.getTrackableValue(sendingPAIOp.get());
if (trackableValue.value.isSendable())
continue;
auto rep = trackableValue.value.getRepresentative().maybeGetValue();
nonSendableOps.push_back(&sendingPAIOp);
if (trackableValue.value.getIsolationRegionInfo().isTaskIsolated() ||
rep == maybeIsolatedValue) {
if (auto capturedValue = findClosureUse(&sendingPAIOp)) {
diagnosticEmitter.emitSendingClosureParamDirectlyIsolated(
callsiteOp, capturedValue->first, capturedValue->second);
return true;
}
}
}
// If we did not find a clear answer in terms of an isolated value, we emit a
// more general error based on:
//
// 1. If we have one non-Sendable value then we know that must be the value.
// 2. Otherwise, we emit a generic captured non-Sendable value error to give
// people something to work off of.
diagnosticEmitter.emitSendingClosureMultipleCapturedOperandError(
callsiteOp->getUser()->getLoc(), nonSendableOps);
return true;
}
bool SentNeverSendableDiagnosticInferrer::initForIsolatedPartialApply(
Operand *op, AbstractClosureExpr *ace,
std::optional<ActorIsolation> actualCallerIsolation) {
auto diagnosticPair = findClosureUse(op);
if (!diagnosticPair)
return false;
auto *diagnosticOp = diagnosticPair->first;
ApplyIsolationCrossing crossing(
*op->getFunction()->getActorIsolation(),
*diagnosticOp->getFunction()->getActorIsolation());
// We do not need to worry about failing to infer a name here since we are
// going to be returning some form of a SILFunctionArgument which is always
// easy to find a name for.
if (auto rootValueAndName = inferNameAndRootHelper(op->get())) {
diagnosticEmitter.emitNamedFunctionArgumentClosure(
diagnosticOp->getUser()->getLoc(), rootValueAndName->first, crossing);
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 SentNeverSendableDiagnosticInferrer::AutoClosureWalker : ASTWalker {
SendNeverSentDiagnosticEmitter &foundTypeInfo;
ValueDecl *targetDecl;
SILIsolationInfo targetDeclIsolationInfo;
SmallPtrSet<Expr *, 8> visitedCallExprDeclRefExprs;
SILLocation captureLoc;
bool isAsyncLet;
AutoClosureWalker(SendNeverSentDiagnosticEmitter &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 SentNeverSendableDiagnosticInferrer::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 'sending' argument.
if (auto fas = FullApplySite::isa(op->getUser())) {
if (fas.getArgumentParameterInfo(*op).hasOption(
SILParameterInfo::Sending)) {
// Before we do anything, lets see if we are passing a sendable closure
// literal. If we do, we want to emit a special error that states which
// captured value caused the actual error.
if (initForSendingPartialApply(fas, op))
return true;
// See if we can infer a name from the value.
if (auto varName = inferNameHelper(op->get())) {
diagnosticEmitter.emitNamedSendingNeverSendableToSendingParam(
loc, *varName);
return true;
}
Type type = op->get()->getType().getASTType();
if (auto *inferredArgExpr =
inferArgumentExprFromApplyExpr(sourceApply, fas, op)) {
type = inferredArgExpr->findOriginalType();
}
diagnosticEmitter.emitTypedSendingNeverSendableToSendingParam(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.
if (auto name = inferNameHelper(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.emitPassToApply(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.emitPassToApply(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!");
if (auto name = inferNameHelper(op->get())) {
diagnosticEmitter.emitNamedSendingReturn(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.getASTAppliedArgIndex(*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;
}
//===----------------------------------------------------------------------===//
// MARK: InOutSendingReturnedError Error Emitter
//===----------------------------------------------------------------------===//
namespace {
class InOutSendingReturnedDiagnosticEmitter {
/// A region analysis function info that we use if we need to determine which
/// incoming block edge had an error upon it.
RegionAnalysisFunctionInfo *raFuncInfo;
/// 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 element number of the 'inout sending' param.
Element inoutSendingParamElement;
/// If we did not return the 'inout sending' param directly but instead
/// returned something else in the same region. This is that value.
///
/// If we actually returned the inout sending parameter this is SILValue().
SILValue returnedValue;
/// If we had a failure due to returning a value that is actor isolated due to
/// it being in the same region as an out parameter. THis is the actor
/// isolation.
SILDynamicMergedIsolationInfo isolationInfo;
bool emittedErrorDiagnostic = false;
public:
class LastValueEnum;
InOutSendingReturnedDiagnosticEmitter(
RegionAnalysisFunctionInfo *raFuncInfo, TermInst *functionExitingInst,
SILValue inoutSendingParam, Element inoutSendingParamElement,
SILValue erroringValue, SILDynamicMergedIsolationInfo isolationInfo)
: raFuncInfo(raFuncInfo), functionExitingInst(functionExitingInst),
inoutSendingParam(inoutSendingParam),
inoutSendingParamElement(inoutSendingParamElement),
returnedValue(erroringValue), isolationInfo(isolationInfo) {}
~InOutSendingReturnedDiagnosticEmitter() {
// If we were supposed to emit a diagnostic and didn't emit an unknown
// pattern error.
if (!emittedErrorDiagnostic)
emitUnknownPatternError();
}
SILFunction *getFunction() const { return inoutSendingParam->getFunction(); }
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
return inoutSendingParam->getType().getConcurrencyDiagnosticBehavior(
getFunction());
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(functionExitingInst,
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getBehaviorLimit());
}
void emit();
/// Called if we return the actual inout sending value.
void emitReturnInOutSendingError(SILDynamicMergedIsolationInfo isolationInfo,
SILLocation loc) {
// We should always be able to find a name for an inout sending param. If we
// do not, emit an unknown pattern error.
auto inoutSendingParamName = inferNameHelper(inoutSendingParam);
if (!inoutSendingParamName) {
return emitUnknownPatternError();
}
diagnoseError(
loc, diag::regionbasedisolation_inout_sending_cannot_be_returned_param,
*inoutSendingParamName)
.limitBehaviorIf(getBehaviorLimit());
if (isolationInfo->isActorIsolated()) {
diagnoseNote(
loc,
diag::
regionbasedisolation_inout_sending_cannot_be_returned_note_actor_param,
*inoutSendingParamName,
isolationInfo->printForDiagnostics(getFunction()));
return;
}
diagnoseNote(
loc,
diag::regionbasedisolation_inout_sending_cannot_be_returned_note_param,
*inoutSendingParamName);
}
void emitValueError(SILValue value,
SILDynamicMergedIsolationInfo isolationInfo,
SILLocation loc) {
// We should always be able to find a name for an inout sending param. If we
// do not, emit an unknown pattern error.
auto inoutSendingParamName = inferNameHelper(inoutSendingParam);
if (!inoutSendingParamName) {
return emitUnknownPatternError();
}
std::optional<Identifier> erroringEltName = inferNameHelper(value);
if (!erroringEltName) {
return emitUnknownPatternError();
}
diagnoseError(
loc, diag::regionbasedisolation_inout_sending_cannot_be_returned_value,
*erroringEltName)
.limitBehaviorIf(getBehaviorLimit());
if (isolationInfo->isActorIsolated()) {
diagnoseNote(
loc,
diag::
regionbasedisolation_inout_sending_cannot_be_returned_note_actor_value,
*erroringEltName, *inoutSendingParamName,
isolationInfo->printForDiagnostics(getFunction()));
return;
}
diagnoseNote(
loc,
diag::regionbasedisolation_inout_sending_cannot_be_returned_note_value,
*erroringEltName, *inoutSendingParamName);
}
/// Print \p value in a standard way. Allow for loc to override the specific
/// location used to emit the diagnostic fi non-standard.
void emitLastValueEnum(const LastValueEnum &value,
SILDynamicMergedIsolationInfo isolationInfo,
SILLocation loc = SILLocation::invalid());
/// Emit an error for use when we are processing the incoming value of a phi
/// argument or of the dynamic value of an indirect out parameter.
bool emitOutParamIncomingValueError(const LastValueEnum &value);
void emitDeclRefError(SILDeclRef declRef,
SILDynamicMergedIsolationInfo isolationInfo,
SILLocation loc) {
// We should always be able to find a name for an inout sending param. If we
// do not, emit an unknown pattern error.
auto inoutSendingParamName = inferNameHelper(inoutSendingParam);
if (!inoutSendingParamName) {
return emitUnknownPatternError();
}
diagnoseError(
loc,
diag::
regionbasedisolation_inout_sending_cannot_be_returned_value_result,
declRef.getAbstractFunctionDecl())
.limitBehaviorIf(getBehaviorLimit());
if (isolationInfo->isActorIsolated()) {
diagnoseNote(
loc,
diag::
regionbasedisolation_inout_sending_cannot_be_returned_note_actor_return_value,
declRef.getAbstractFunctionDecl(), *inoutSendingParamName,
isolationInfo.printForDiagnostics(getFunction()));
return;
}
diagnoseNote(
loc,
diag::
regionbasedisolation_inout_sending_cannot_be_returned_note_return_value,
declRef.getAbstractFunctionDecl(), *inoutSendingParamName);
}
ASTContext &getASTContext() const {
return 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)...)
.warnUntilLanguageMode(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
static SILValue lookThroughAccess(SILValue value) {
while (true) {
// We look through access, copies, and loads.
if (auto *bai = dyn_cast<BeginAccessInst>(value)) {
value = bai->getOperand();
continue;
}
if (auto *cvi = dyn_cast<CopyValueInst>(value)) {
value = cvi->getOperand();
continue;
}
if (auto *bbi = dyn_cast<BeginBorrowInst>(value);
bbi && !bbi->isFromVarDecl()) {
value = bbi->getOperand();
continue;
}
if (auto *mvi = dyn_cast<MoveValueInst>(value);
mvi && !mvi->isFromVarDecl()) {
value = mvi->getOperand();
continue;
}
if (auto *li = dyn_cast<LoadInst>(value)) {
value = li->getOperand();
continue;
}
if (auto *lbi = dyn_cast<LoadBorrowInst>(value)) {
value = lbi->getOperand();
continue;
}
return value;
}
}
class InOutSendingReturnedDiagnosticEmitter::LastValueEnum {
public:
enum Kind {
None,
ApplySite,
CopyAddr,
Store,
Value,
ReturnTerm,
};
private:
Kind kind;
std::variant<FullApplySite, SILValue, CopyAddrInst *, StoreInst *, Operand *>
value;
LastValueEnum() : kind(Kind::None), value() {}
LastValueEnum(FullApplySite inst) : kind(Kind::ApplySite), value(inst) {}
LastValueEnum(SILValue value) : kind(Kind::Value), value(value) {}
LastValueEnum(CopyAddrInst *inst) : kind(Kind::CopyAddr), value(inst) {}
LastValueEnum(StoreInst *inst) : kind(Kind::Store), value(inst) {}
LastValueEnum(Operand *branchUse) : kind(Kind::ReturnTerm), value(branchUse) {
assert(isa<TermInst>(branchUse->getUser()));
assert(branchUse->getUser()->getLoc().getKind() == SILLocation::ReturnKind);
}
public:
static LastValueEnum get(SILValue value);
static LastValueEnum get(SILInstruction *inst);
static LastValueEnum get(Operand *use);
static bool isSupported(Operand *use);
operator bool() const { return getKind() != Kind::None; }
Kind getKind() const { return kind; }
bool isApplySite() const { return getKind() == Kind::ApplySite; }
bool isValue() const { return getKind() == Kind::Value; }
bool isStore() const { return getKind() == Kind::Store; }
bool isCopyAddr() const { return getKind() == Kind::CopyAddr; }
/// Is this a terminator instruction that is an incoming value to an epilogue
/// block and thus forms part of the return from a function.
bool isReturnTerm() const { return getKind() == Kind::ReturnTerm; }
FullApplySite getFullApplySite() const {
return std::get<FullApplySite>(value);
}
SILDeclRef getDeclRef() const {
return getFullApplySite().getCalleeFunction()->getDeclRef();
}
SILValue getValue() const {
switch (getKind()) {
case None:
case ApplySite:
return SILValue();
case CopyAddr:
return getCopyAddr()->getAllOperands()[CopyLikeInstruction::Src].get();
case Store:
return getStore()->getAllOperands()[CopyLikeInstruction::Src].get();
case Value:
return std::get<SILValue>(value);
case ReturnTerm:
return std::get<Operand *>(value)->get();
}
}
CopyAddrInst *getCopyAddr() const { return std::get<CopyAddrInst *>(value); }
StoreInst *getStore() const { return std::get<StoreInst *>(value); }
Operand *getReturnTerm() const { return std::get<Operand *>(value); }
SILBasicBlock *getParentBlock() const {
switch (getKind()) {
case None:
return nullptr;
case ApplySite:
return getFullApplySite()->getParent();
case Value:
return getValue()->getParentBlock();
case CopyAddr:
return getCopyAddr()->getParentBlock();
case Store:
return getStore()->getParentBlock();
case ReturnTerm:
return getReturnTerm()->getParentBlock();
}
}
SILInstruction *getDefiningInsertionPoint() const {
switch (getKind()) {
case None:
return nullptr;
case ApplySite:
return *getFullApplySite();
case Value:
return getValue()->getDefiningInsertionPoint();
case CopyAddr:
return getCopyAddr();
case Store:
return getStore();
case ReturnTerm:
return getReturnTerm()->getUser();
}
}
SILLocation getLoc() const {
switch (getKind()) {
case None:
return SILLocation::invalid();
case ApplySite:
case Value:
case CopyAddr:
case Store:
case ReturnTerm:
return getDefiningInsertionPoint()->getLoc();
}
}
bool operator==(SILValue otherValue) const {
switch (getKind()) {
case None:
case ApplySite:
return false;
case CopyAddr:
case Store:
case Value:
case ReturnTerm:
return lookThroughAccess(getValue()) == lookThroughAccess(otherValue);
}
}
static bool
findLastValuesForOutParam(SILFunctionArgument *fArg,
SmallVectorImpl<LastValueEnum> &lastValues);
void print(llvm::raw_ostream &os) const {
switch (getKind()) {
case None:
os << "none\n";
return;
case ApplySite:
os << "apply site: " << *getFullApplySite();
return;
case CopyAddr:
os << "copy_addr: " << *getCopyAddr();
return;
case Store:
os << "store: " << *getStore();
return;
case Value:
os << "value: " << *getValue();
break;
case ReturnTerm:
os << "return term: " << *getReturnTerm()->getUser();
}
}
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
};
InOutSendingReturnedDiagnosticEmitter::LastValueEnum
InOutSendingReturnedDiagnosticEmitter::LastValueEnum::get(
SILInstruction *user) {
if (auto *cai = dyn_cast<CopyAddrInst>(user)) {
return {cai};
}
if (auto *si = dyn_cast<StoreInst>(user)) {
return {si};
}
if (auto fas = FullApplySite::isa(user)) {
return {fas};
}
return {};
}
InOutSendingReturnedDiagnosticEmitter::LastValueEnum
InOutSendingReturnedDiagnosticEmitter::LastValueEnum::get(Operand *use) {
if (auto value = get(use->getUser()))
return value;
if (auto *ti = dyn_cast<TermInst>(use->getUser())) {
if (ti->getLoc().getKind() == SILLocation::ReturnKind) {
return {use};
}
}
return {};
}
bool InOutSendingReturnedDiagnosticEmitter::LastValueEnum::isSupported(
Operand *use) {
if (auto fas = FullApplySite::isa(use->getUser())) {
if (fas.isIndirectResultOperand(*use)) {
auto *fn = fas.getCalleeFunction();
return fn && fn->getDeclRef();
}
}
if (auto *ti = dyn_cast<TermInst>(use->getUser())) {
if (ti->getLoc().getKind() == SILLocation::ReturnKind) {
return true;
}
}
return isa<CopyAddrInst, StoreInst>(use->getUser());
}
InOutSendingReturnedDiagnosticEmitter::LastValueEnum
InOutSendingReturnedDiagnosticEmitter::LastValueEnum::get(SILValue value) {
if (auto *inst = value->getDefiningInstruction()) {
if (auto e = get(inst))
return e;
}
return {value};
}
void InOutSendingReturnedDiagnosticEmitter::emitLastValueEnum(
const LastValueEnum &value, SILDynamicMergedIsolationInfo isolationInfo,
SILLocation loc) {
switch (value.getKind()) {
case LastValueEnum::None:
llvm::report_fatal_error("Found .none enum elt");
case LastValueEnum::ApplySite:
emitDeclRefError(value.getDeclRef(), isolationInfo,
loc ? loc : value.getLoc());
return;
case LastValueEnum::Store:
case LastValueEnum::CopyAddr:
emitValueError(value.getValue(), isolationInfo, loc ? loc : value.getLoc());
return;
case LastValueEnum::Value:
emitValueError(value.getValue(), isolationInfo,
loc ? loc : functionExitingInst->getLoc());
return;
case LastValueEnum::ReturnTerm:
emitValueError(value.getValue(), isolationInfo, loc ? loc : value.getLoc());
return;
}
llvm::report_fatal_error("Should never hit this");
}
bool InOutSendingReturnedDiagnosticEmitter::emitOutParamIncomingValueError(
const LastValueEnum &finalValue) {
auto *block = finalValue.getParentBlock();
auto state = raFuncInfo->getBlockState(block);
assert(state.isNonNull());
// Grab the exit partition of the block. We make a copy here on purpose
// since it /is/ possible for DiagnosticEvaluator to modify
auto workingExitPartition = state.get()->getExitPartition();
// See if given the exit partition of the block if we would emit an error
// when we execute an InOutSendingAtFunctionExit partition op.
SmallFrozenMultiMap<Operand *, RequireInst, 8> sendingOpToRequireInstMultiMap;
SmallVector<PartitionOpError, 2> errors;
DiagnosticEvaluator evaluator(workingExitPartition, raFuncInfo,
sendingOpToRequireInstMultiMap, errors,
raFuncInfo->getSendingOperandToStateMap());
PartitionOp op = PartitionOp::InOutSendingAtFunctionExit(
inoutSendingParamElement, finalValue.getDefiningInsertionPoint());
evaluator.apply(op);
// Check if we got an error. If we did not, then this is not one of the
// guilty code paths. Continue.
if (errors.empty()) {
return false;
}
// Loop through the errors to find our InOutReturnedError. We emit one error
// per return... but we emit for multiple returns separate diagnostics.
while (!errors.empty()) {
auto err = errors.pop_back_val();
if (err.getKind() != PartitionOpError::InOutSendingReturned)
continue;
auto castError = std::move(err).getInOutSendingReturnedError();
if (castError.inoutSendingElement != inoutSendingParamElement)
continue;
// Ok, we found an error. Lets emit it and return early.
if (finalValue == inoutSendingParam) {
emitReturnInOutSendingError(isolationInfo, finalValue.getLoc());
return true;
}
// Force us to use the location of the finalValue. We do not want to use the
// function exiting inst.
emitLastValueEnum(finalValue, isolationInfo, finalValue.getLoc());
return true;
}
return false;
}
void InOutSendingReturnedDiagnosticEmitter::emit() {
// Check if we had a separate erroring value that is our returned value. If we
// do not then we just returned the 'inout sending' parameter. Emit a special
// message and return.
if (inoutSendingParam == returnedValue) {
return emitReturnInOutSendingError(isolationInfo,
functionExitingInst->getLoc());
}
// Before we do anything, see if we have a phi argument that is an epilogue
// phi. In such a case, we want to process the incoming values.
if (auto *phi = dyn_cast<SILPhiArgument>(returnedValue)) {
bool handledValue = false;
phi->visitIncomingPhiOperands([&](Operand *op) {
if (op->getUser()->getLoc().getKind() != SILLocation::ReturnKind)
return true;
handledValue |=
emitOutParamIncomingValueError(LastValueEnum::get(op->get()));
return true;
});
if (handledValue)
return;
}
// Check if we have an indirect out parameter as our erroring returned value.
auto *fArg = dyn_cast<SILFunctionArgument>(returnedValue);
if (!fArg || !fArg->isIndirectResult()) {
// If we do not have one... emit a normal error.
return emitLastValueEnum(LastValueEnum::get(returnedValue), isolationInfo);
}
SmallVector<LastValueEnum, 8> finalValues;
if (!LastValueEnum::findLastValuesForOutParam(fArg, finalValues))
return emitLastValueEnum(LastValueEnum::get(returnedValue), isolationInfo);
// If we have a single value, then we have found our value.
if (finalValues.size() == 1) {
if (finalValues[0] == inoutSendingParam) {
return emitReturnInOutSendingError(isolationInfo,
finalValues[0].getLoc());
}
emitLastValueEnum(finalValues[0], isolationInfo);
return;
}
// If we have multiple finalValues, then that means that we had multiple
// return values. In such a case, we must have a single epilog block
// that our finalValues jointly dominate. That means that we need to
// determine which block the error actually occured along and emit an
// error for the value in that block. We know we are going to emit an
// error, so we can spend more time finding the better diagnostic since
// we are going to fail.
for (auto finalValue : finalValues) {
emitOutParamIncomingValueError(finalValue);
}
}
bool InOutSendingReturnedDiagnosticEmitter::LastValueEnum::
findLastValuesForOutParam(SILFunctionArgument *fArg,
SmallVectorImpl<LastValueEnum> &lastValues) {
assert(fArg->isIndirectResult());
SmallVector<SILBasicBlock *, 8> visitedBlocks;
SSAPrunedLiveness prunedLiveRange(fArg->getFunction(), &visitedBlocks);
// Look for copy_addr and store uses into fArg. They should all be complete
// stores.
prunedLiveRange.initializeDef(fArg);
for (auto *use : fArg->getUses()) {
if (LastValueEnum::isSupported(use)) {
prunedLiveRange.updateForUse(use->getUser(), false /*lifetime ending*/);
continue;
}
return false;
}
PrunedLivenessBoundary boundary;
prunedLiveRange.computeBoundary(boundary);
for (auto *user : boundary.lastUsers) {
if (auto e = LastValueEnum::get(user)) {
lastValues.emplace_back(e);
continue;
}
llvm::report_fatal_error("Out of sync with earlier check");
}
return true;
}
//===----------------------------------------------------------------------===//
// MARK: InOutSendingNotDisconnected Error Emitter
//===----------------------------------------------------------------------===//
namespace {
class InOutSendingNotDisconnectedDiagnosticEmitter {
/// 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;
bool emittedErrorDiagnostic = false;
public:
InOutSendingNotDisconnectedDiagnosticEmitter(
TermInst *functionExitingInst, SILValue inoutSendingParam,
SILDynamicMergedIsolationInfo actorIsolatedRegionInfo)
: functionExitingInst(functionExitingInst),
inoutSendingParam(inoutSendingParam),
actorIsolatedRegionInfo(actorIsolatedRegionInfo) {}
~InOutSendingNotDisconnectedDiagnosticEmitter() {
// If we were supposed to emit a diagnostic and didn't emit an unknown
// pattern error.
if (!emittedErrorDiagnostic)
emitUnknownPatternError();
}
SILFunction *getFunction() const { return inoutSendingParam->getFunction(); }
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
return inoutSendingParam->getType().getConcurrencyDiagnosticBehavior(
getFunction());
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(functionExitingInst,
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getBehaviorLimit());
}
void emit();
ASTContext &getASTContext() const {
return 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)...)
.warnUntilLanguageMode(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 = inferNameHelper(inoutSendingParam);
if (!varName) {
return emitUnknownPatternError();
}
// Then emit the note with greater context.
auto descriptiveKindStr =
actorIsolatedRegionInfo.printForDiagnostics(getFunction());
diagnoseError(
functionExitingInst,
diag::regionbasedisolation_inout_sending_cannot_be_actor_isolated,
*varName, descriptiveKindStr)
.limitBehaviorIf(getBehaviorLimit());
diagnoseNote(
functionExitingInst,
diag::regionbasedisolation_inout_sending_cannot_be_actor_isolated_note,
*varName, descriptiveKindStr);
}
//===----------------------------------------------------------------------===//
// MARK: AssignIsolatedIntoSendingResultDiagnosticEmitter
//===----------------------------------------------------------------------===//
namespace {
class AssignIsolatedIntoSendingResultDiagnosticEmitter {
/// The use that actually caused the send.
Operand *srcOperand;
/// The specific out sending result.
SILFunctionArgument *outSendingResult;
/// The never-sent value that is in the same region as \p
/// outSendingResult.
SILValue neverSentValue;
/// The region info that describes the dynamic dataflow derived isolation
/// region info for the never-sent value.
///
/// This is equal to the merge of the IsolationRegionInfo from all elements in
/// the never-sent value's region when the error was diagnosed.
SILDynamicMergedIsolationInfo isolatedValueIsolationRegionInfo;
bool emittedErrorDiagnostic = false;
public:
AssignIsolatedIntoSendingResultDiagnosticEmitter(
Operand *srcOperand, SILFunctionArgument *outSendingResult,
SILValue neverSentValue,
SILDynamicMergedIsolationInfo isolatedValueIsolationRegionInfo)
: srcOperand(srcOperand), outSendingResult(outSendingResult),
neverSentValue(neverSentValue),
isolatedValueIsolationRegionInfo(isolatedValueIsolationRegionInfo) {}
~AssignIsolatedIntoSendingResultDiagnosticEmitter() {
// If we were supposed to emit a diagnostic and didn't emit an unknown
// pattern error.
if (!emittedErrorDiagnostic)
emitUnknownPatternError();
}
SILFunction *getFunction() const { return srcOperand->getFunction(); }
std::optional<DiagnosticBehavior> getConcurrencyDiagnosticBehavior() const {
return outSendingResult->getType().getConcurrencyDiagnosticBehavior(
getFunction());
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(srcOperand->getUser(),
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getConcurrencyDiagnosticBehavior());
}
void emit();
ASTContext &getASTContext() const {
return srcOperand->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)...)
.warnUntilLanguageMode(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 diagnoseError(Operand *op, Diag<T...> diag, U &&...args) {
return diagnoseError(op->getUser()->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)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(Operand *op, Diag<T...> diag, U &&...args) {
return diagnoseNote(op->getUser()->getLoc(), diag,
std::forward<U>(args)...);
}
};
} // namespace
/// Look through values looking for our out parameter. We want to tightly
/// control this to be conservative... so we handroll this.
static SILValue findOutParameter(SILValue v) {
while (true) {
SILValue temp = v;
if (auto *initOpt = dyn_cast<InitEnumDataAddrInst>(temp)) {
if (initOpt->getElement()->getParentEnum() ==
initOpt->getFunction()->getASTContext().getOptionalDecl()) {
temp = initOpt->getOperand();
}
}
if (temp == v) {
return v;
}
v = temp;
}
}
void AssignIsolatedIntoSendingResultDiagnosticEmitter::emit() {
// Then emit the note with greater context.
auto descriptiveKindStr =
isolatedValueIsolationRegionInfo.printForDiagnostics(getFunction());
// Grab the var name if we can find it.
if (auto varName = VariableNameInferrer::inferName(srcOperand->get())) {
// In general, when we do an assignment like this, we assume that srcOperand
// and our outSendingResult have the same type. This doesn't always happen
// though especially if our outSendingResult is used as an out parameter of
// a class_method. Check for such a case and if so, add to the end of our
// string a path component for that class_method.
if (srcOperand->get()->getType() != outSendingResult->getType()) {
if (auto fas = FullApplySite::isa(srcOperand->getUser())) {
if (fas.hasSelfArgument() &&
fas.getSelfArgument() == srcOperand->get() &&
fas.getNumIndirectSILResults() == 1) {
// First check if our function argument is exactly our out parameter.
bool canEmit = outSendingResult == fas.getIndirectSILResults()[0];
// If that fails, see if we are storing into a temporary
// alloc_stack. In such a case, find the root value that the temporary
// is initialized to and see if that is our target function
// argument. In such a case, we also want to add the decl name to our
// type.
if (!canEmit) {
canEmit = outSendingResult ==
findOutParameter(fas.getIndirectSILResults()[0]);
}
if (canEmit) {
if (auto *callee =
dyn_cast_or_null<MethodInst>(fas.getCalleeOrigin())) {
SmallString<64> resultingString;
resultingString.append(varName->str());
resultingString += '.';
resultingString += VariableNameInferrer::getNameFromDecl(
callee->getMember().getDecl());
varName = fas->getFunction()->getASTContext().getIdentifier(
resultingString);
}
}
}
}
}
diagnoseError(
srcOperand,
diag::regionbasedisolation_out_sending_cannot_be_actor_isolated_named,
*varName, descriptiveKindStr)
.limitBehaviorIf(getConcurrencyDiagnosticBehavior());
diagnoseNote(
srcOperand,
diag::
regionbasedisolation_out_sending_cannot_be_actor_isolated_note_named,
*varName, descriptiveKindStr);
return;
}
Type type = neverSentValue->getType().getASTType();
diagnoseError(
srcOperand,
diag::regionbasedisolation_out_sending_cannot_be_actor_isolated_type,
type, descriptiveKindStr)
.limitBehaviorIf(getConcurrencyDiagnosticBehavior());
diagnoseNote(
srcOperand,
diag::regionbasedisolation_out_sending_cannot_be_actor_isolated_note_type,
type, descriptiveKindStr);
diagnoseNote(srcOperand, diag::regionbasedisolation_type_is_non_sendable,
type);
}
//===----------------------------------------------------------------------===//
// MARK: NonSendableIsolationCrossingResult Emitter
//===----------------------------------------------------------------------===//
/// Add Fix-It text for the given nominal type to adopt Sendable.
static void addSendableFixIt(const NominalTypeDecl *nominal,
InFlightDiagnostic &diag, bool unchecked) {
if (nominal->getInherited().empty()) {
SourceLoc fixItLoc = nominal->getBraces().Start;
diag.fixItInsert(fixItLoc,
unchecked ? ": @unchecked Sendable" : ": Sendable");
} else {
auto fixItLoc = nominal->getInherited().getEndLoc();
diag.fixItInsertAfter(fixItLoc,
unchecked ? ", @unchecked Sendable" : ", Sendable");
}
}
/// Add Fix-It text for the given generic param declaration type to adopt
/// Sendable.
static void addSendableFixIt(const GenericTypeParamDecl *genericArgument,
InFlightDiagnostic &diag) {
if (genericArgument->getInherited().empty()) {
auto fixItLoc = genericArgument->getLoc();
diag.fixItInsertAfter(fixItLoc, ": Sendable");
} else {
auto fixItLoc = genericArgument->getInherited().getEndLoc();
diag.fixItInsertAfter(fixItLoc, " & Sendable");
}
}
namespace {
struct NonSendableIsolationCrossingResultDiagnosticEmitter {
RegionAnalysisValueMap &valueMap;
using Error = PartitionOpError::NonSendableIsolationCrossingResultError;
Error error;
bool emittedErrorDiagnostic = false;
/// The value assigned as the equivalence class representative. It is
/// guaranteed to be from the isolation crossing function since we never treat
/// isolation crossing functions as being look through.
SILValue representative;
NonSendableIsolationCrossingResultDiagnosticEmitter(
RegionAnalysisValueMap &valueMap, Error &&error)
: valueMap(valueMap), error(std::move(error)),
representative(valueMap.getRepresentative(error.returnValueElement)) {}
SILFunction *getFunction() const {
return error.op->getSourceInst()->getFunction();
}
void emit();
ASTContext &getASTContext() const {
return error.op->getSourceInst()->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)...)
.warnUntilLanguageMode(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)...);
}
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
return representative->getType().getConcurrencyDiagnosticBehavior(
representative->getFunction());
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(error.op->getSourceInst(),
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getBehaviorLimit());
}
Type getType() const {
if (auto *applyExpr =
error.op->getSourceInst()->getLoc().getAsASTNode<ApplyExpr>()) {
return applyExpr->getType();
}
// If we do not have an ApplyExpr, see if we can just infer the type from
// the SILFunction type. This is only used in SIL test cases.
if (auto fas = FullApplySite::isa(error.op->getSourceInst())) {
return fas.getSubstCalleeType()
->getAllResultsSubstType(fas.getModule(),
fas.getFunction()->getTypeExpansionContext())
.getASTType();
}
return Type();
}
const ValueDecl *getCalledDecl() const {
if (auto *applyExpr =
error.op->getSourceInst()->getLoc().getAsASTNode<ApplyExpr>()) {
if (auto calledValue =
applyExpr->getCalledValue(true /*look through conversions*/)) {
return calledValue;
}
}
return nullptr;
}
std::optional<ApplyIsolationCrossing> getIsolationCrossing() const {
if (auto *applyExpr =
error.op->getSourceInst()->getLoc().getAsASTNode<ApplyExpr>()) {
if (auto isolationCrossing = applyExpr->getIsolationCrossing()) {
return *isolationCrossing;
}
}
// If we have a SIL based test case, just return the actual isolation
// crossing.
if (auto fas = FullApplySite::isa(error.op->getSourceInst())) {
if (auto isolationCrossing = fas.getIsolationCrossing())
return *isolationCrossing;
}
return {};
}
};
} // namespace
void NonSendableIsolationCrossingResultDiagnosticEmitter::emit() {
auto isolationCrossing = getIsolationCrossing();
if (!isolationCrossing)
return emitUnknownPatternError();
auto calleeIsolationStr = SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing->getCalleeIsolation());
auto callerIsolationStr = SILIsolationInfo::printActorIsolationForDiagnostics(
getFunction(), isolationCrossing->getCallerIsolation());
auto type = getType();
if (getCalledDecl()) {
diagnoseError(error.op->getSourceInst(),
diag::rbi_isolation_crossing_result, type, calleeIsolationStr,
getCalledDecl(), callerIsolationStr)
.limitBehaviorIf(getBehaviorLimit());
} else {
diagnoseError(error.op->getSourceInst(),
diag::rbi_isolation_crossing_result_no_decl, type,
calleeIsolationStr, callerIsolationStr)
.limitBehaviorIf(getBehaviorLimit());
}
if (type->is<FunctionType>()) {
diagnoseNote(error.op->getSourceInst(),
diag::rbi_nonsendable_function_type);
return;
}
auto *moduleDecl = error.op->getSourceInst()->getModule().getSwiftModule();
if (auto *nominal = type->getNominalOrBoundGenericNominal()) {
// If the nominal type is in the current module, suggest adding `Sendable`
// if it makes sense.
if (nominal->getParentModule() == moduleDecl &&
(isa<StructDecl>(nominal) || isa<EnumDecl>(nominal))) {
auto note = nominal->diagnose(diag::rbi_add_nominal_sendable_conformance,
nominal);
addSendableFixIt(nominal, note, /*unchecked*/ false);
} else {
nominal->diagnose(diag::rbi_non_sendable_nominal, nominal);
}
return;
}
if (auto genericArchetype = type->getAs<ArchetypeType>()) {
auto interfaceType = genericArchetype->getInterfaceType();
if (auto genericParamType = interfaceType->getAs<GenericTypeParamType>()) {
auto *genericParamTypeDecl = genericParamType->getDecl();
if (genericParamTypeDecl &&
genericParamTypeDecl->getModuleContext() == moduleDecl) {
auto diag = genericParamTypeDecl->diagnose(
diag::rbi_add_generic_parameter_sendable_conformance, type);
addSendableFixIt(genericParamTypeDecl, diag);
return;
}
}
}
}
//===----------------------------------------------------------------------===//
// MARK: InOutSendingParametersInSameRegionError
//===----------------------------------------------------------------------===//
namespace {
class InOutSendingParametersInSameRegionDiagnosticEmitter {
/// The function exiting inst where the 'inout sending' parameter was actor
/// isolated.
TermInst *functionExitingInst;
/// The first 'inout sending' param in the region.
SILValue firstInOutSendingParam;
/// The second 'inout sending' param in the region.
SILValue secondInOutSendingParam;
bool emittedErrorDiagnostic = false;
public:
InOutSendingParametersInSameRegionDiagnosticEmitter(
TermInst *functionExitingInst, SILValue firstInOutSendingParam,
SILValue secondInOutSendingParam)
: functionExitingInst(functionExitingInst),
firstInOutSendingParam(firstInOutSendingParam),
secondInOutSendingParam(secondInOutSendingParam) {}
~InOutSendingParametersInSameRegionDiagnosticEmitter() {
// If we were supposed to emit a diagnostic and didn't emit an unknown
// pattern error.
if (!emittedErrorDiagnostic)
emitUnknownPatternError();
}
SILFunction *getFunction() const {
return functionExitingInst->getFunction();
}
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
auto first =
firstInOutSendingParam->getType().getConcurrencyDiagnosticBehavior(
getFunction());
auto second =
secondInOutSendingParam->getType().getConcurrencyDiagnosticBehavior(
getFunction());
if (!first)
return second;
if (!second)
return first;
return first->merge(*second);
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(functionExitingInst,
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getBehaviorLimit());
}
void emit();
ASTContext &getASTContext() const {
return 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)...)
.warnUntilLanguageMode(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 InOutSendingParametersInSameRegionDiagnosticEmitter::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 firstName = inferNameHelper(firstInOutSendingParam);
if (!firstName) {
return emitUnknownPatternError();
}
auto secondName = inferNameHelper(secondInOutSendingParam);
if (!secondName) {
return emitUnknownPatternError();
}
diagnoseError(functionExitingInst,
diag::regionbasedisolation_inout_sending_in_same_region,
*firstName, *secondName)
.limitBehaviorIf(getBehaviorLimit());
diagnoseNote(functionExitingInst,
diag::regionbasedisolation_inout_sending_in_same_region_note,
*firstName, *secondName);
}
//===----------------------------------------------------------------------===//
// MARK: Top Level Entrypoint
//===----------------------------------------------------------------------===//
void SendNonSendableImpl::emitVerbatimErrors() {
for (auto &erasedError : foundVerbatimErrors) {
switch (erasedError.getKind()) {
case PartitionOpError::UnknownCodePattern:
case PartitionOpError::LocalUseAfterSend:
case PartitionOpError::InOutSendingNotInitializedAtExit:
llvm_unreachable("Handled elsewhere");
case PartitionOpError::AssignNeverSendableIntoSendingResult: {
auto error =
std::move(erasedError).getAssignNeverSendableIntoSendingResultError();
REGIONBASEDISOLATION_LOG(error.print(llvm::dbgs(), info->getValueMap()));
AssignIsolatedIntoSendingResultDiagnosticEmitter emitter(
error.op->getSourceOp(), error.destValue, error.srcValue,
error.srcIsolationRegionInfo);
emitter.emit();
continue;
}
case PartitionOpError::InOutSendingNotDisconnectedAtExit: {
auto error =
std::move(erasedError).getInOutSendingNotDisconnectedAtExitError();
auto inoutSendingVal =
info->getValueMap().getRepresentative(error.inoutSendingElement);
auto isolationRegionInfo = error.isolationInfo;
REGIONBASEDISOLATION_LOG(error.print(llvm::dbgs(), info->getValueMap()));
InOutSendingNotDisconnectedDiagnosticEmitter emitter(
cast<TermInst>(error.op->getSourceInst()), inoutSendingVal,
isolationRegionInfo);
emitter.emit();
continue;
}
case PartitionOpError::InOutSendingReturned: {
auto error = std::move(erasedError).getInOutSendingReturnedError();
auto inoutSendingVal =
info->getValueMap().getRepresentative(error.inoutSendingElement);
auto returnedValue =
info->getValueMap().getRepresentative(error.returnedValue);
REGIONBASEDISOLATION_LOG(error.print(llvm::dbgs(), info->getValueMap()));
InOutSendingReturnedDiagnosticEmitter emitter(
info, cast<TermInst>(error.op->getSourceInst()), inoutSendingVal,
error.inoutSendingElement, returnedValue, error.isolationInfo);
emitter.emit();
continue;
}
case PartitionOpError::SentNeverSendable: {
auto e = std::move(erasedError).getSentNeverSendableError();
REGIONBASEDISOLATION_LOG(e.print(llvm::dbgs(), info->getValueMap()));
SentNeverSendableDiagnosticInferrer diagnosticInferrer(
info->getValueMap(), std::move(e));
diagnosticInferrer.run();
continue;
}
case PartitionOpError::NonSendableIsolationCrossingResult: {
auto e =
std::move(erasedError).getNonSendableIsolationCrossingResultError();
REGIONBASEDISOLATION_LOG(e.print(llvm::dbgs(), info->getValueMap()));
NonSendableIsolationCrossingResultDiagnosticEmitter diagnosticInferrer(
info->getValueMap(), std::move(e));
diagnosticInferrer.emit();
continue;
}
case PartitionOpError::InOutSendingParametersInSameRegion: {
auto e =
std::move(erasedError).getInOutSendingParametersInSameRegionError();
REGIONBASEDISOLATION_LOG(e.print(llvm::dbgs(), info->getValueMap()));
auto firstParam =
info->getValueMap().getRepresentativeValue(e.firstInoutSendingParam);
// Walk through our secondInOutSendingParams and use one that is not
// ignore.
for (auto paramElt : e.otherInOutSendingParams) {
auto paramValue =
info->getValueMap().getRepresentativeValue(paramElt).getValue();
if (auto behavior =
paramValue->getType().getConcurrencyDiagnosticBehavior(
info->getFunction());
behavior && *behavior == DiagnosticBehavior::Ignore) {
continue;
}
InOutSendingParametersInSameRegionDiagnosticEmitter diagnosticEmitter(
cast<TermInst>(e.op->getSourceInst()), firstParam.getValue(),
paramValue);
diagnosticEmitter.emit();
}
continue;
}
}
llvm_unreachable("Covered switch isn't covered?!");
}
}
/// 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 SendNonSendableImpl::emitDiagnostics() {
auto *function = info->getFunction();
REGIONBASEDISOLATION_LOG(llvm::dbgs() << "Emitting diagnostics for function "
<< function->getName() << "\n");
runDiagnosticEvaluator();
emitUseAfterSendDiagnostics();
emitVerbatimErrors();
}
namespace {
class SendNonSendable : public SILFunctionTransform {
void run() override {
SILFunction *function = getFunction();
auto *functionInfo = getAnalysis<RegionAnalysis>()->get(function);
if (!functionInfo->isSupportedFunction()) {
REGIONBASEDISOLATION_LOG(llvm::dbgs()
<< "===> SKIPPING UNSUPPORTED FUNCTION: "
<< function->getName() << '\n');
return;
}
REGIONBASEDISOLATION_LOG(
llvm::dbgs() << "===> PROCESSING: " << function->getName() << '\n');
SendNonSendableImpl impl(functionInfo);
impl.emitDiagnostics();
}
};
} // end anonymous namespace
SILTransform *swift::createSendNonSendable() { return new SendNonSendable(); }