mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
This will let me look up the dynamic isolation region associated with \p elt while performing dataflow.
1452 lines
54 KiB
C++
1452 lines
54 KiB
C++
//===--- TransferNonSendable.cpp ------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#define DEBUG_TYPE "transfer-non-sendable"
|
|
|
|
#include "swift/AST/ASTWalker.h"
|
|
#include "swift/AST/DiagnosticsSIL.h"
|
|
#include "swift/AST/Expr.h"
|
|
#include "swift/AST/Type.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 "llvm/ADT/DenseMap.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
using namespace swift;
|
|
using namespace swift::PartitionPrimitives;
|
|
using namespace swift::PatternMatch;
|
|
using namespace swift::regionanalysisimpl;
|
|
|
|
namespace {
|
|
|
|
using TransferringOperandSetFactory = Partition::TransferringOperandSetFactory;
|
|
using TrackableValueID = PartitionPrimitives::Element;
|
|
using Region = PartitionPrimitives::Region;
|
|
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Utilities
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
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;
|
|
}
|
|
|
|
static std::optional<Identifier> inferNameFromValue(SILValue value) {
|
|
auto *fn = value->getFunction();
|
|
if (!fn)
|
|
return {};
|
|
VariableNameInferrer::Options options;
|
|
options |= VariableNameInferrer::Flag::InferSelfThroughAllAccessors;
|
|
SmallString<64> resultingName;
|
|
VariableNameInferrer inferrer(fn, options, resultingName);
|
|
if (!inferrer.inferByWalkingUsesToDefsReturningRoot(value))
|
|
return {};
|
|
return fn->getASTContext().getIdentifier(resultingName);
|
|
}
|
|
|
|
static std::optional<std::pair<Identifier, SILValue>>
|
|
inferNameAndRootFromValue(SILValue value) {
|
|
auto *fn = value->getFunction();
|
|
if (!fn)
|
|
return {};
|
|
VariableNameInferrer::Options options;
|
|
options |= VariableNameInferrer::Flag::InferSelfThroughAllAccessors;
|
|
SmallString<64> resultingName;
|
|
VariableNameInferrer inferrer(fn, options, resultingName);
|
|
SILValue rootValue = inferrer.inferByWalkingUsesToDefsReturningRoot(value);
|
|
if (!rootValue)
|
|
return {};
|
|
return {{fn->getASTContext().getIdentifier(resultingName), rootValue}};
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Diagnostics
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseError(ASTContext &context, SourceLoc loc,
|
|
Diag<T...> diag, U &&...args) {
|
|
return std::move(context.Diags.diagnose(loc, diag, std::forward<U>(args)...)
|
|
.warnUntilSwiftVersion(6));
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseError(ASTContext &context, SILLocation loc,
|
|
Diag<T...> diag, U &&...args) {
|
|
return ::diagnoseError(context, loc.getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseError(const PartitionOp &op, Diag<T...> diag,
|
|
U &&...args) {
|
|
return ::diagnoseError(op.getSourceInst()->getFunction()->getASTContext(),
|
|
op.getSourceLoc().getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseError(const Operand *op, Diag<T...> diag,
|
|
U &&...args) {
|
|
return ::diagnoseError(op->getUser()->getFunction()->getASTContext(),
|
|
op->getUser()->getLoc().getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseError(const SILInstruction *inst,
|
|
Diag<T...> diag, U &&...args) {
|
|
return ::diagnoseError(inst->getFunction()->getASTContext(),
|
|
inst->getLoc().getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseNote(ASTContext &context, SourceLoc loc,
|
|
Diag<T...> diag, U &&...args) {
|
|
return context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseNote(ASTContext &context, SILLocation loc,
|
|
Diag<T...> diag, U &&...args) {
|
|
return ::diagnoseNote(context, loc.getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseNote(const PartitionOp &op, Diag<T...> diag,
|
|
U &&...args) {
|
|
return ::diagnoseNote(op.getSourceInst()->getFunction()->getASTContext(),
|
|
op.getSourceLoc().getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseNote(const Operand *op, Diag<T...> diag,
|
|
U &&...args) {
|
|
return ::diagnoseNote(op->getUser()->getFunction()->getASTContext(),
|
|
op->getUser()->getLoc().getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
static InFlightDiagnostic diagnoseNote(const SILInstruction *inst,
|
|
Diag<T...> diag, U &&...args) {
|
|
return ::diagnoseNote(inst->getFunction()->getASTContext(),
|
|
inst->getLoc().getSourceLoc(), diag,
|
|
std::forward<U>(args)...);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Require Liveness
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
class BlockLivenessInfo {
|
|
// Generation counter so we do not need to reallocate.
|
|
unsigned generation = 0;
|
|
SILInstruction *firstRequireInst = nullptr;
|
|
|
|
void resetIfNew(unsigned newGeneration) {
|
|
if (generation == newGeneration)
|
|
return;
|
|
generation = newGeneration;
|
|
firstRequireInst = nullptr;
|
|
}
|
|
|
|
public:
|
|
SILInstruction *getInst(unsigned callerGeneration) {
|
|
resetIfNew(callerGeneration);
|
|
return firstRequireInst;
|
|
}
|
|
|
|
void setInst(unsigned callerGeneration, SILInstruction *newValue) {
|
|
resetIfNew(callerGeneration);
|
|
firstRequireInst = newValue;
|
|
}
|
|
};
|
|
|
|
/// We only want to emit errors for the first requires along a path from a
|
|
/// transfer instruction. We discover this by walking from user blocks to
|
|
struct RequireLiveness {
|
|
unsigned generation;
|
|
SILInstruction *transferInst;
|
|
BasicBlockData<BlockLivenessInfo> &blockLivenessInfo;
|
|
InstructionSet allRequires;
|
|
InstructionSetWithSize finalRequires;
|
|
|
|
/// If we have requires in the def block before our transfer, this is the
|
|
/// first require.
|
|
SILInstruction *firstRequireBeforeTransferInDefBlock = nullptr;
|
|
|
|
RequireLiveness(unsigned generation, Operand *transferOp,
|
|
BasicBlockData<BlockLivenessInfo> &blockLivenessInfo)
|
|
: generation(generation), transferInst(transferOp->getUser()),
|
|
blockLivenessInfo(blockLivenessInfo),
|
|
allRequires(transferOp->getParentFunction()),
|
|
finalRequires(transferOp->getParentFunction()) {}
|
|
|
|
template <typename Collection>
|
|
void process(Collection collection);
|
|
|
|
/// Attempt to process requireInst for our def block. Returns false if
|
|
/// requireInst was before our def and we need to do interprocedural
|
|
/// processing. Returns true if requireInst was after our transferInst and we
|
|
/// were able to appropriately determine if we should emit it or not.
|
|
void processDefBlock();
|
|
|
|
/// Process all requires in block, updating blockLivenessInfo.
|
|
void processNonDefBlock(SILBasicBlock *block);
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void RequireLiveness::processDefBlock() {
|
|
LLVM_DEBUG(llvm::dbgs() << " Processing def block!\n");
|
|
// First walk from the beginning of the block to the transfer instruction to
|
|
// see if we have any requires before our def. Once we find one, we can skip
|
|
// the traversal and jump straight to the transfer.
|
|
for (auto ii = transferInst->getParent()->begin(),
|
|
ie = transferInst->getIterator();
|
|
ii != ie; ++ii) {
|
|
if (allRequires.contains(&*ii) && !firstRequireBeforeTransferInDefBlock) {
|
|
firstRequireBeforeTransferInDefBlock = &*ii;
|
|
LLVM_DEBUG(llvm::dbgs() << " Found transfer before def: "
|
|
<< *firstRequireBeforeTransferInDefBlock);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Then walk from our transferInst to the end of the block looking for the
|
|
// first require inst. Once we find it... return.
|
|
//
|
|
// NOTE: We start walking at the transferInst since the transferInst could use
|
|
// the requireInst as well.
|
|
for (auto ii = transferInst->getIterator(),
|
|
ie = transferInst->getParent()->end();
|
|
ii != ie; ++ii) {
|
|
if (!allRequires.contains(&*ii))
|
|
continue;
|
|
|
|
finalRequires.insert(&*ii);
|
|
LLVM_DEBUG(llvm::dbgs() << " Found transfer after def: " << *ii);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void RequireLiveness::processNonDefBlock(SILBasicBlock *block) {
|
|
// Walk from the bottom to the top... assigning to our block state.
|
|
auto blockState = blockLivenessInfo.get(block);
|
|
for (auto &inst : llvm::make_range(block->rbegin(), block->rend())) {
|
|
if (!finalRequires.contains(&inst))
|
|
continue;
|
|
blockState.get()->setInst(generation, &inst);
|
|
}
|
|
}
|
|
|
|
template <typename Collection>
|
|
void RequireLiveness::process(Collection requireInstList) {
|
|
LLVM_DEBUG(llvm::dbgs() << "==> Performing Require Liveness for: "
|
|
<< *transferInst);
|
|
|
|
// Then put all of our requires into our allRequires set.
|
|
BasicBlockWorklist initializingWorklist(transferInst->getFunction());
|
|
for (auto *require : requireInstList) {
|
|
LLVM_DEBUG(llvm::dbgs() << " Require Inst: " << *require);
|
|
allRequires.insert(require);
|
|
initializingWorklist.pushIfNotVisited(require->getParent());
|
|
}
|
|
|
|
// Then process our def block to see if we have any requires before and after
|
|
// the transferInst...
|
|
processDefBlock();
|
|
|
|
// If we found /any/ requries after the transferInst, we can bail early since
|
|
// that is guaranteed to dominate all further requires.
|
|
if (!finalRequires.empty()) {
|
|
LLVM_DEBUG(
|
|
llvm::dbgs()
|
|
<< " Found transfer after def in def block! Exiting early!\n");
|
|
return;
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << " Did not find transfer after def in def "
|
|
"block! Walking blocks!\n");
|
|
|
|
// If we found a transfer in the def block before our def, add it to the block
|
|
// state for the def.
|
|
if (firstRequireBeforeTransferInDefBlock) {
|
|
LLVM_DEBUG(
|
|
llvm::dbgs()
|
|
<< " Found a require before transfer! Adding to block state!\n");
|
|
auto blockState = blockLivenessInfo.get(transferInst->getParent());
|
|
blockState.get()->setInst(generation, firstRequireBeforeTransferInDefBlock);
|
|
}
|
|
|
|
// Then for each require block that isn't a def block transfer, find the
|
|
// earliest transfer inst.
|
|
while (auto *requireBlock = initializingWorklist.pop()) {
|
|
auto blockState = blockLivenessInfo.get(requireBlock);
|
|
for (auto &inst : *requireBlock) {
|
|
if (!allRequires.contains(&inst))
|
|
continue;
|
|
LLVM_DEBUG(llvm::dbgs() << " Mapping Block bb"
|
|
<< requireBlock->getDebugID() << " to: " << inst);
|
|
blockState.get()->setInst(generation, &inst);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Then walk from our def block looking for setInst blocks.
|
|
auto *transferBlock = transferInst->getParent();
|
|
BasicBlockWorklist worklist(transferInst->getFunction());
|
|
for (auto *succBlock : transferBlock->getSuccessorBlocks())
|
|
worklist.pushIfNotVisited(succBlock);
|
|
|
|
while (auto *next = worklist.pop()) {
|
|
// Check if we found an earliest requires... if so, add that to final
|
|
// requires and continue. We don't want to visit successors.
|
|
auto blockState = blockLivenessInfo.get(next);
|
|
if (auto *inst = blockState.get()->getInst(generation)) {
|
|
finalRequires.insert(inst);
|
|
continue;
|
|
}
|
|
|
|
// Do not look at successors of the transfer block.
|
|
if (next == transferBlock)
|
|
continue;
|
|
|
|
// Otherwise, we did not find a requires and need to search further
|
|
// successors.
|
|
for (auto *succBlock : next->getSuccessorBlocks())
|
|
worklist.pushIfNotVisited(succBlock);
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Forward Declaration Of TransferNonSendableImpl
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
struct TransferredNonTransferrableInfo {
|
|
/// The use that actually caused the transfer.
|
|
Operand *transferredOperand;
|
|
|
|
/// The non-transferrable value that is in the same region as \p
|
|
/// transferredOperand.get().
|
|
llvm::PointerUnion<SILValue, SILInstruction *> nonTransferrable;
|
|
|
|
TransferredNonTransferrableInfo(Operand *transferredOperand,
|
|
SILValue nonTransferrableValue)
|
|
: transferredOperand(transferredOperand),
|
|
nonTransferrable(nonTransferrableValue) {}
|
|
TransferredNonTransferrableInfo(Operand *transferredOperand,
|
|
SILInstruction *nonTransferrableInst)
|
|
: transferredOperand(transferredOperand),
|
|
nonTransferrable(nonTransferrableInst) {}
|
|
};
|
|
|
|
class TransferNonSendableImpl {
|
|
RegionAnalysisFunctionInfo *regionInfo;
|
|
SmallFrozenMultiMap<Operand *, SILInstruction *, 8>
|
|
transferOpToRequireInstMultiMap;
|
|
SmallVector<TransferredNonTransferrableInfo, 8> transferredNonTransferrable;
|
|
|
|
public:
|
|
TransferNonSendableImpl(RegionAnalysisFunctionInfo *regionInfo)
|
|
: regionInfo(regionInfo) {}
|
|
|
|
void emitDiagnostics();
|
|
|
|
private:
|
|
void runDiagnosticEvaluator();
|
|
|
|
void emitUseAfterTransferDiagnostics();
|
|
void emitTransferredNonTransferrableDiagnostics();
|
|
};
|
|
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: UseAfterTransfer Diagnostic Inference
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
class UseAfterTransferDiagnosticEmitter {
|
|
Operand *transferOp;
|
|
SmallVectorImpl<SILInstruction *> &requireInsts;
|
|
bool emittedErrorDiagnostic = false;
|
|
|
|
public:
|
|
UseAfterTransferDiagnosticEmitter(
|
|
Operand *transferOp, SmallVectorImpl<SILInstruction *> &requireInsts)
|
|
: transferOp(transferOp), requireInsts(requireInsts) {}
|
|
|
|
~UseAfterTransferDiagnosticEmitter() {
|
|
// If we were supposed to emit a diagnostic and didn't emit an unknown
|
|
// pattern error.
|
|
if (!emittedErrorDiagnostic)
|
|
emitUnknownPatternError();
|
|
}
|
|
|
|
void emitNamedIsolationCrossingError(SILLocation loc, Identifier name,
|
|
ApplyIsolationCrossing isolationCrossing,
|
|
SILLocation variableDefinedLoc) {
|
|
// Emit the short error.
|
|
diagnoseError(loc, diag::regionbasedisolation_named_transfer_yields_race,
|
|
name)
|
|
.highlight(loc.getSourceRange());
|
|
|
|
// Then emit the note with greater context.
|
|
diagnoseNote(loc,
|
|
diag::regionbasedisolation_named_info_transfer_yields_race,
|
|
name, isolationCrossing.getCallerIsolation(),
|
|
isolationCrossing.getCalleeIsolation());
|
|
// Then emit the note about where the variable is defined.
|
|
diagnoseNote(variableDefinedLoc, diag::variable_defined_here,
|
|
false /*variable*/);
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void emitTypedIsolationCrossing(SILLocation loc, Type inferredType,
|
|
ApplyIsolationCrossing isolationCrossing) {
|
|
diagnoseError(
|
|
loc, diag::regionbasedisolation_transfer_yields_race_with_isolation,
|
|
inferredType, isolationCrossing.getCallerIsolation(),
|
|
isolationCrossing.getCalleeIsolation())
|
|
.highlight(loc.getSourceRange());
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void emitUseOfStringlyTransferredValue(SILLocation loc, Type inferredType) {
|
|
diagnoseError(
|
|
loc,
|
|
diag::
|
|
regionbasedisolation_transfer_yields_race_stronglytransferred_binding,
|
|
inferredType)
|
|
.highlight(loc.getSourceRange());
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void emitTypedRaceWithUnknownIsolationCrossing(SILLocation loc,
|
|
Type inferredType) {
|
|
diagnoseError(loc,
|
|
diag::regionbasedisolation_transfer_yields_race_no_isolation,
|
|
inferredType)
|
|
.highlight(loc.getSourceRange());
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void emitTypedIsolationCrossingDueToCapture(
|
|
SILLocation loc, Type inferredType,
|
|
ApplyIsolationCrossing isolationCrossing) {
|
|
diagnoseError(loc, diag::regionbasedisolation_isolated_capture_yields_race,
|
|
inferredType, isolationCrossing.getCalleeIsolation(),
|
|
isolationCrossing.getCallerIsolation())
|
|
.highlight(loc.getSourceRange());
|
|
emitRequireInstDiagnostics();
|
|
}
|
|
|
|
void emitUnknownPatternError() {
|
|
diagnoseError(transferOp->getUser(),
|
|
diag::regionbasedisolation_unknown_pattern);
|
|
}
|
|
|
|
private:
|
|
ASTContext &getASTContext() const {
|
|
return transferOp->getFunction()->getASTContext();
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
emittedErrorDiagnostic = true;
|
|
return std::move(getASTContext()
|
|
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
|
|
.warnUntilSwiftVersion(6));
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
|
|
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
void emitRequireInstDiagnostics() {
|
|
// Now actually emit the require notes.
|
|
while (!requireInsts.empty()) {
|
|
auto *require = requireInsts.pop_back_val();
|
|
diagnoseNote(require, diag::regionbasedisolation_maybe_race)
|
|
.highlight(require->getLoc().getSourceRange());
|
|
}
|
|
}
|
|
};
|
|
|
|
class UseAfterTransferDiagnosticInferrer {
|
|
Operand *transferOp;
|
|
UseAfterTransferDiagnosticEmitter diagnosticEmitter;
|
|
RegionAnalysisValueMap &valueMap;
|
|
SILLocation baseLoc = SILLocation::invalid();
|
|
Type baseInferredType;
|
|
|
|
struct Walker;
|
|
|
|
public:
|
|
UseAfterTransferDiagnosticInferrer(
|
|
Operand *transferOp, SmallVectorImpl<SILInstruction *> &requireInsts,
|
|
RegionAnalysisValueMap &valueMap)
|
|
: transferOp(transferOp), diagnosticEmitter(transferOp, requireInsts),
|
|
valueMap(valueMap), baseLoc(transferOp->getUser()->getLoc()),
|
|
baseInferredType(transferOp->get()->getType().getASTType()) {}
|
|
void infer();
|
|
|
|
private:
|
|
bool initForIsolatedPartialApply(Operand *op, AbstractClosureExpr *ace);
|
|
|
|
void initForApply(Operand *op, ApplyExpr *expr);
|
|
void initForAutoclosure(Operand *op, AutoClosureExpr *expr);
|
|
|
|
Expr *getFoundExprForSelf(ApplyExpr *sourceApply) {
|
|
if (auto callExpr = dyn_cast<CallExpr>(sourceApply))
|
|
if (auto calledExpr =
|
|
dyn_cast<DotSyntaxCallExpr>(callExpr->getDirectCallee()))
|
|
return calledExpr->getBase();
|
|
return nullptr;
|
|
}
|
|
|
|
Expr *getFoundExprForParam(ApplyExpr *sourceApply, unsigned argNum) {
|
|
auto *expr = sourceApply->getArgs()->getExpr(argNum);
|
|
|
|
// If we have an erasure expression, lets use the original type. We do
|
|
// this since we are not saying the specific parameter that is the
|
|
// issue and we are using the type to explain it to the user.
|
|
if (auto *erasureExpr = dyn_cast<ErasureExpr>(expr))
|
|
expr = erasureExpr->getSubExpr();
|
|
|
|
return expr;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
bool UseAfterTransferDiagnosticInferrer::initForIsolatedPartialApply(
|
|
Operand *op, AbstractClosureExpr *ace) {
|
|
SmallVector<std::tuple<CapturedValue, unsigned, ApplyIsolationCrossing>, 8>
|
|
foundCapturedIsolationCrossing;
|
|
ace->getIsolationCrossing(foundCapturedIsolationCrossing);
|
|
if (foundCapturedIsolationCrossing.empty())
|
|
return false;
|
|
|
|
unsigned opIndex = ApplySite(op->getUser()).getAppliedArgIndex(*op);
|
|
for (auto &p : foundCapturedIsolationCrossing) {
|
|
if (std::get<1>(p) == opIndex) {
|
|
diagnosticEmitter.emitTypedIsolationCrossingDueToCapture(
|
|
RegularLocation(std::get<0>(p).getLoc()), baseInferredType,
|
|
std::get<2>(p));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void UseAfterTransferDiagnosticInferrer::initForApply(Operand *op,
|
|
ApplyExpr *sourceApply) {
|
|
auto isolationCrossing = sourceApply->getIsolationCrossing().value();
|
|
|
|
// Grab out full apply site and see if we can find a better expr.
|
|
SILInstruction *i = const_cast<SILInstruction *>(op->getUser());
|
|
auto fai = FullApplySite::isa(i);
|
|
|
|
assert(!fai.getArgumentConvention(*op).isIndirectOutParameter() &&
|
|
"An indirect out parameter is never transferred");
|
|
auto *foundExpr = inferArgumentExprFromApplyExpr(sourceApply, fai, op);
|
|
|
|
auto inferredArgType =
|
|
foundExpr ? foundExpr->findOriginalType() : baseInferredType;
|
|
diagnosticEmitter.emitTypedIsolationCrossing(baseLoc, inferredArgType,
|
|
isolationCrossing);
|
|
}
|
|
|
|
struct UseAfterTransferDiagnosticInferrer::Walker : ASTWalker {
|
|
UseAfterTransferDiagnosticInferrer &foundTypeInfo;
|
|
ValueDecl *targetDecl;
|
|
SmallPtrSet<Expr *, 8> visitedCallExprDeclRefExprs;
|
|
|
|
Walker(UseAfterTransferDiagnosticInferrer &foundTypeInfo,
|
|
ValueDecl *targetDecl)
|
|
: foundTypeInfo(foundTypeInfo), targetDecl(targetDecl) {}
|
|
|
|
Expr *lookThroughExpr(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, add it as
|
|
// something without isolation crossing.
|
|
if (!visitedCallExprDeclRefExprs.count(declRef)) {
|
|
if (declRef->getDecl() == targetDecl) {
|
|
visitedCallExprDeclRefExprs.insert(declRef);
|
|
foundTypeInfo.diagnosticEmitter
|
|
.emitTypedRaceWithUnknownIsolationCrossing(
|
|
foundTypeInfo.baseLoc, declRef->findOriginalType());
|
|
return Action::Continue(expr);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auto *callExpr = dyn_cast<CallExpr>(expr)) {
|
|
if (auto isolationCrossing = callExpr->getIsolationCrossing()) {
|
|
// Search callExpr's arguments to see if we have our targetDecl.
|
|
auto *argList = callExpr->getArgs();
|
|
for (auto pair : llvm::enumerate(argList->getArgExprs())) {
|
|
auto *arg = lookThroughExpr(pair.value());
|
|
if (auto *declRef = dyn_cast<DeclRefExpr>(arg)) {
|
|
if (declRef->getDecl() == targetDecl) {
|
|
// Found our target!
|
|
visitedCallExprDeclRefExprs.insert(declRef);
|
|
foundTypeInfo.diagnosticEmitter.emitTypedIsolationCrossing(
|
|
foundTypeInfo.baseLoc, declRef->findOriginalType(),
|
|
*isolationCrossing);
|
|
return Action::Continue(expr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Action::Continue(expr);
|
|
}
|
|
};
|
|
|
|
void UseAfterTransferDiagnosticInferrer::infer() {
|
|
// Otherwise, see if our operand's instruction is a transferring parameter.
|
|
if (auto fas = FullApplySite::isa(transferOp->getUser())) {
|
|
assert(!fas.getArgumentConvention(*transferOp).isIndirectOutParameter() &&
|
|
"We should never transfer an indirect out parameter");
|
|
if (fas.getArgumentParameterInfo(*transferOp)
|
|
.hasOption(SILParameterInfo::Transferring)) {
|
|
return diagnosticEmitter.emitUseOfStringlyTransferredValue(
|
|
baseLoc, baseInferredType);
|
|
}
|
|
}
|
|
|
|
auto loc = transferOp->getUser()->getLoc();
|
|
|
|
// If we have a partial_apply that is actor isolated, see if we found a
|
|
// transfer error due to us transferring a value into it.
|
|
if (auto *ace = loc.getAsASTNode<AbstractClosureExpr>()) {
|
|
if (ace->getActorIsolation().isActorIsolated()) {
|
|
if (initForIsolatedPartialApply(transferOp, ace)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auto *sourceApply = loc.getAsASTNode<ApplyExpr>()) {
|
|
// Before we do anything further, see if we can find a name and emit a name
|
|
// error.
|
|
if (auto rootValueAndName = inferNameAndRootFromValue(transferOp->get())) {
|
|
if (auto *svi =
|
|
dyn_cast<SingleValueInstruction>(rootValueAndName->second)) {
|
|
return diagnosticEmitter.emitNamedIsolationCrossingError(
|
|
baseLoc, rootValueAndName->first,
|
|
*sourceApply->getIsolationCrossing(), svi->getLoc());
|
|
}
|
|
|
|
if (auto *fArg =
|
|
dyn_cast<SILFunctionArgument>(rootValueAndName->second)) {
|
|
return diagnosticEmitter.emitNamedIsolationCrossingError(
|
|
baseLoc, rootValueAndName->first,
|
|
*sourceApply->getIsolationCrossing(),
|
|
RegularLocation(fArg->getDecl()->getLoc()));
|
|
}
|
|
}
|
|
|
|
// Otherwise, try to infer from the ApplyExpr.
|
|
return initForApply(transferOp, sourceApply);
|
|
}
|
|
|
|
if (auto fas = FullApplySite::isa(transferOp->getUser())) {
|
|
if (auto isolationCrossing = fas.getIsolationCrossing()) {
|
|
return diagnosticEmitter.emitTypedIsolationCrossing(
|
|
baseLoc, baseInferredType, *isolationCrossing);
|
|
}
|
|
}
|
|
|
|
auto *autoClosureExpr = loc.getAsASTNode<AutoClosureExpr>();
|
|
if (!autoClosureExpr) {
|
|
return diagnosticEmitter.emitUnknownPatternError();
|
|
}
|
|
|
|
auto *i = transferOp->getUser();
|
|
auto pai = ApplySite::isa(i);
|
|
unsigned captureIndex = pai.getAppliedArgIndex(*transferOp);
|
|
|
|
auto captureInfo =
|
|
autoClosureExpr->getCaptureInfo().getCaptures()[captureIndex];
|
|
auto *captureDecl = captureInfo.getDecl();
|
|
Walker walker(*this, captureDecl);
|
|
autoClosureExpr->walk(walker);
|
|
}
|
|
|
|
// Top level entrypoint for use after transfer diagnostics.
|
|
void TransferNonSendableImpl::emitUseAfterTransferDiagnostics() {
|
|
auto *function = regionInfo->getFunction();
|
|
BasicBlockData<BlockLivenessInfo> blockLivenessInfo(function);
|
|
// We use a generation counter so we can lazily reset blockLivenessInfo
|
|
// since we cannot clear it without iterating over it.
|
|
unsigned blockLivenessInfoGeneration = 0;
|
|
|
|
if (transferOpToRequireInstMultiMap.empty())
|
|
return;
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Emitting use after transfer diagnostics.\n");
|
|
|
|
for (auto [transferOp, requireInsts] :
|
|
transferOpToRequireInstMultiMap.getRange()) {
|
|
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "Transfer Op. Number: " << transferOp->getOperandNumber()
|
|
<< ". User: " << *transferOp->getUser());
|
|
|
|
// Then look for our requires before we emit any error. We want to emit a
|
|
// single we don't understand error if we do not find the require.
|
|
bool didEmitRequireNote = false;
|
|
InstructionSet requireInstsUnique(function);
|
|
RequireLiveness liveness(blockLivenessInfoGeneration, transferOp,
|
|
blockLivenessInfo);
|
|
++blockLivenessInfoGeneration;
|
|
liveness.process(requireInsts);
|
|
|
|
SmallVector<SILInstruction *, 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) {
|
|
diagnoseError(transferOp, diag::regionbasedisolation_unknown_pattern);
|
|
continue;
|
|
}
|
|
|
|
UseAfterTransferDiagnosticInferrer diagnosticInferrer(
|
|
transferOp, requireInstsForError, regionInfo->getValueMap());
|
|
diagnosticInferrer.infer();
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Transfer NonTransferrable Diagnostic Inference
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
class TransferNonTransferrableDiagnosticEmitter {
|
|
TransferredNonTransferrableInfo info;
|
|
RegionAnalysisFunctionInfo *regionInfo;
|
|
bool emittedErrorDiagnostic = false;
|
|
|
|
public:
|
|
TransferNonTransferrableDiagnosticEmitter(
|
|
TransferredNonTransferrableInfo info,
|
|
RegionAnalysisFunctionInfo *regionInfo)
|
|
: info(info), regionInfo(regionInfo) {}
|
|
|
|
~TransferNonTransferrableDiagnosticEmitter() {
|
|
if (!emittedErrorDiagnostic) {
|
|
emitUnknownPatternError();
|
|
}
|
|
}
|
|
|
|
Operand *getOperand() const { return info.transferredOperand; }
|
|
|
|
SILValue getNonTransferrableValue() const {
|
|
return info.nonTransferrable.dyn_cast<SILValue>();
|
|
}
|
|
|
|
SILInstruction *getNonTransferringActorIntroducingInst() const {
|
|
return info.nonTransferrable.dyn_cast<SILInstruction *>();
|
|
}
|
|
|
|
/// Return the isolation region info for \p getNonTransferrableValue().
|
|
ValueIsolationRegionInfo getIsolationRegionInfo() const {
|
|
// If we have a value, then lookup the value isolation info.
|
|
if (auto value = getNonTransferrableValue()) {
|
|
return regionInfo->getValueMap().getIsolationRegion(value);
|
|
}
|
|
|
|
// If we have an instruction, then we have an actor in
|
|
auto *inst = getNonTransferringActorIntroducingInst();
|
|
auto trackableValue =
|
|
regionInfo->getValueMap().getTrackableValueForActorIntroducingInst(
|
|
inst);
|
|
if (!trackableValue)
|
|
return {};
|
|
return trackableValue->getIsolationRegionInfo();
|
|
}
|
|
|
|
void emitUnknownPatternError() {
|
|
diagnoseError(getOperand()->getUser(),
|
|
diag::regionbasedisolation_unknown_pattern);
|
|
}
|
|
|
|
void emitUnknownUse(SILLocation loc) {
|
|
// TODO: This will eventually be an unknown pattern error.
|
|
diagnoseError(
|
|
loc, diag::regionbasedisolation_task_or_actor_isolated_transferred);
|
|
}
|
|
|
|
void emitFunctionArgumentApply(SILLocation loc, Type type,
|
|
ApplyIsolationCrossing crossing) {
|
|
SmallString<64> descriptiveKindStr;
|
|
{
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
getIsolationRegionInfo().printForDiagnostics(os);
|
|
}
|
|
diagnoseError(loc, diag::regionbasedisolation_arg_transferred,
|
|
StringRef(descriptiveKindStr), type,
|
|
crossing.getCalleeIsolation())
|
|
.highlight(getOperand()->getUser()->getLoc().getSourceRange());
|
|
|
|
if (getIsolationRegionInfo().isTaskIsolated()) {
|
|
auto *fArg =
|
|
cast<SILFunctionArgument>(info.nonTransferrable.get<SILValue>());
|
|
if (fArg->getDecl()) {
|
|
diagnoseNote(
|
|
fArg->getDecl()->getLoc(),
|
|
diag::regionbasedisolation_isolated_since_in_same_region_basename,
|
|
"task-isolated", fArg->getDecl()->getBaseName());
|
|
}
|
|
}
|
|
}
|
|
|
|
void emitFunctionArgumentClosure(SourceLoc loc, Type type,
|
|
ApplyIsolationCrossing crossing) {
|
|
diagnoseError(loc, diag::regionbasedisolation_arg_transferred,
|
|
"task-isolated", type, crossing.getCalleeIsolation())
|
|
.highlight(getOperand()->getUser()->getLoc().getSourceRange());
|
|
// Only emit the note if our value is different from the function
|
|
// argument.
|
|
auto rep = regionInfo->getValueMap()
|
|
.getTrackableValue(getOperand()->get())
|
|
.getRepresentative();
|
|
if (!info.nonTransferrable.is<SILValue>() ||
|
|
rep.maybeGetValue() == info.nonTransferrable.get<SILValue>())
|
|
return;
|
|
auto *fArg =
|
|
cast<SILFunctionArgument>(info.nonTransferrable.get<SILValue>());
|
|
diagnoseNote(
|
|
fArg->getDecl()->getLoc(),
|
|
diag::regionbasedisolation_isolated_since_in_same_region_basename,
|
|
"task-isolated", fArg->getDecl()->getBaseName());
|
|
}
|
|
|
|
void emitFunctionArgumentApplyStronglyTransferred(SILLocation loc,
|
|
Type type) {
|
|
SmallString<64> descriptiveKindStr;
|
|
{
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
getIsolationRegionInfo().printForDiagnostics(os);
|
|
}
|
|
auto diag =
|
|
diag::regionbasedisolation_arg_passed_to_strongly_transferred_param;
|
|
diagnoseError(loc, diag, descriptiveKindStr, type)
|
|
.highlight(getOperand()->getUser()->getLoc().getSourceRange());
|
|
}
|
|
|
|
void emitNamedOnlyError(SILLocation loc, Identifier name) {
|
|
diagnoseError(loc, diag::regionbasedisolation_named_transfer_yields_race,
|
|
name)
|
|
.highlight(getOperand()->getUser()->getLoc().getSourceRange());
|
|
}
|
|
|
|
void emitNamedIsolation(SILLocation loc, Identifier name,
|
|
ApplyIsolationCrossing isolationCrossing) {
|
|
emitNamedOnlyError(loc, name);
|
|
diagnoseNote(loc,
|
|
diag::regionbasedisolation_named_transfer_non_transferrable,
|
|
name, isolationCrossing.getCallerIsolation(),
|
|
isolationCrossing.getCalleeIsolation());
|
|
}
|
|
|
|
void emitNamedFunctionArgumentApplyStronglyTransferred(SILLocation loc,
|
|
Identifier varName) {
|
|
emitNamedOnlyError(loc, varName);
|
|
SmallString<64> descriptiveKindStr;
|
|
{
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
getIsolationRegionInfo().printForDiagnostics(os);
|
|
}
|
|
auto diag =
|
|
diag::regionbasedisolation_named_transfer_into_transferring_param;
|
|
diagnoseNote(loc, diag, descriptiveKindStr, varName);
|
|
}
|
|
|
|
void emitNamedTransferringReturn(SILLocation loc, Identifier varName) {
|
|
emitNamedOnlyError(loc, varName);
|
|
SmallString<64> descriptiveKindStr;
|
|
{
|
|
llvm::raw_svector_ostream os(descriptiveKindStr);
|
|
getIsolationRegionInfo().printForDiagnostics(os);
|
|
}
|
|
auto diag =
|
|
diag::regionbasedisolation_named_notransfer_transfer_into_result;
|
|
diagnoseNote(loc, diag, descriptiveKindStr, varName);
|
|
}
|
|
|
|
private:
|
|
ASTContext &getASTContext() const {
|
|
return getOperand()->getFunction()->getASTContext();
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
emittedErrorDiagnostic = true;
|
|
return std::move(getASTContext()
|
|
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
|
|
.warnUntilSwiftVersion(6));
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
|
|
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
|
|
template <typename... T, typename... U>
|
|
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
|
|
U &&...args) {
|
|
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
|
|
}
|
|
};
|
|
|
|
class TransferNonTransferrableDiagnosticInferrer {
|
|
TransferNonTransferrableDiagnosticEmitter diagnosticEmitter;
|
|
|
|
public:
|
|
TransferNonTransferrableDiagnosticInferrer(
|
|
TransferredNonTransferrableInfo info,
|
|
RegionAnalysisFunctionInfo *regionInfo)
|
|
: diagnosticEmitter(info, regionInfo) {}
|
|
|
|
/// 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:
|
|
bool initForIsolatedPartialApply(Operand *op, AbstractClosureExpr *ace);
|
|
};
|
|
|
|
} // namespace
|
|
|
|
bool TransferNonTransferrableDiagnosticInferrer::initForIsolatedPartialApply(
|
|
Operand *op, AbstractClosureExpr *ace) {
|
|
SmallVector<std::tuple<CapturedValue, unsigned, ApplyIsolationCrossing>, 8>
|
|
foundCapturedIsolationCrossing;
|
|
ace->getIsolationCrossing(foundCapturedIsolationCrossing);
|
|
if (foundCapturedIsolationCrossing.empty())
|
|
return false;
|
|
|
|
unsigned opIndex = ApplySite(op->getUser()).getAppliedArgIndex(*op);
|
|
for (auto &p : foundCapturedIsolationCrossing) {
|
|
if (std::get<1>(p) == opIndex) {
|
|
Type type = std::get<0>(p).getDecl()->getInterfaceType();
|
|
diagnosticEmitter.emitFunctionArgumentClosure(std::get<0>(p).getLoc(),
|
|
type, std::get<2>(p));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TransferNonTransferrableDiagnosticInferrer::run() {
|
|
// We need to find the isolation info.
|
|
auto *op = diagnosticEmitter.getOperand();
|
|
auto loc = op->getUser()->getLoc();
|
|
|
|
if (auto *sourceApply = loc.getAsASTNode<ApplyExpr>()) {
|
|
// First see if we have a transferring argument.
|
|
if (auto fas = FullApplySite::isa(op->getUser())) {
|
|
if (fas.getArgumentParameterInfo(*op).hasOption(
|
|
SILParameterInfo::Transferring)) {
|
|
|
|
// See if we can infer a name from the value.
|
|
SmallString<64> resultingName;
|
|
if (auto varName = inferNameFromValue(op->get())) {
|
|
diagnosticEmitter.emitNamedFunctionArgumentApplyStronglyTransferred(
|
|
loc, *varName);
|
|
return true;
|
|
}
|
|
|
|
Type type = op->get()->getType().getASTType();
|
|
if (auto *inferredArgExpr =
|
|
inferArgumentExprFromApplyExpr(sourceApply, fas, op)) {
|
|
type = inferredArgExpr->findOriginalType();
|
|
}
|
|
diagnosticEmitter.emitFunctionArgumentApplyStronglyTransferred(loc,
|
|
type);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// First try to get the apply from the isolation crossing.
|
|
auto isolation = sourceApply->getIsolationCrossing();
|
|
|
|
// If we could not infer an isolation...
|
|
if (!isolation) {
|
|
// Otherwise, emit a "we don't know error" that tells the user to file a
|
|
// bug.
|
|
diagnoseError(op->getUser(), diag::regionbasedisolation_unknown_pattern);
|
|
return false;
|
|
}
|
|
assert(isolation && "Expected non-null");
|
|
|
|
// See if we can infer a name from the value.
|
|
SmallString<64> resultingName;
|
|
if (auto name = inferNameFromValue(op->get())) {
|
|
diagnosticEmitter.emitNamedIsolation(loc, *name, *isolation);
|
|
return true;
|
|
}
|
|
|
|
// Attempt to find the specific sugared ASTType if we can to emit a better
|
|
// diagnostic.
|
|
Type type = op->get()->getType().getASTType();
|
|
if (auto fas = FullApplySite::isa(op->getUser())) {
|
|
if (auto *inferredArgExpr =
|
|
inferArgumentExprFromApplyExpr(sourceApply, fas, op)) {
|
|
type = inferredArgExpr->findOriginalType();
|
|
}
|
|
}
|
|
|
|
diagnosticEmitter.emitFunctionArgumentApply(loc, type, *isolation);
|
|
return true;
|
|
}
|
|
|
|
if (auto *ace = loc.getAsASTNode<AbstractClosureExpr>()) {
|
|
if (ace->getActorIsolation().isActorIsolated()) {
|
|
if (initForIsolatedPartialApply(op, ace)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// See if we are in SIL and have an apply site specified isolation.
|
|
if (auto fas = FullApplySite::isa(op->getUser())) {
|
|
if (auto isolation = fas.getIsolationCrossing()) {
|
|
diagnosticEmitter.emitFunctionArgumentApply(
|
|
loc, op->get()->getType().getASTType(), *isolation);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (auto *ri = dyn_cast<ReturnInst>(op->getUser())) {
|
|
auto fType = ri->getFunction()->getLoweredFunctionType();
|
|
if (fType->getNumResults() &&
|
|
fType->getResults()[0].hasOption(SILResultInfo::IsTransferring)) {
|
|
assert(llvm::all_of(fType->getResults(),
|
|
[](SILResultInfo resultInfo) {
|
|
return resultInfo.hasOption(
|
|
SILResultInfo::IsTransferring);
|
|
}) &&
|
|
"All result info must be the same... if that changes... update "
|
|
"this code!");
|
|
SmallString<64> resultingName;
|
|
if (auto name = inferNameFromValue(op->get())) {
|
|
diagnosticEmitter.emitNamedTransferringReturn(loc, *name);
|
|
return true;
|
|
}
|
|
} else {
|
|
assert(llvm::none_of(fType->getResults(),
|
|
[](SILResultInfo resultInfo) {
|
|
return resultInfo.hasOption(
|
|
SILResultInfo::IsTransferring);
|
|
}) &&
|
|
"All result info must be the same... if that changes... update "
|
|
"this code!");
|
|
}
|
|
}
|
|
|
|
diagnosticEmitter.emitUnknownUse(loc);
|
|
return true;
|
|
}
|
|
|
|
// Top level emission for transfer non transferable diagnostic.
|
|
void TransferNonSendableImpl::emitTransferredNonTransferrableDiagnostics() {
|
|
if (transferredNonTransferrable.empty())
|
|
return;
|
|
|
|
LLVM_DEBUG(
|
|
llvm::dbgs() << "Emitting transfer non transferrable diagnostics.\n");
|
|
|
|
for (auto info : transferredNonTransferrable) {
|
|
TransferNonTransferrableDiagnosticInferrer diagnosticInferrer(info,
|
|
regionInfo);
|
|
diagnosticInferrer.run();
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Diagnostic Evaluator
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
struct DiagnosticEvaluator final
|
|
: PartitionOpEvaluatorBaseImpl<DiagnosticEvaluator> {
|
|
RegionAnalysisFunctionInfo *info;
|
|
SmallFrozenMultiMap<Operand *, SILInstruction *, 8>
|
|
&transferOpToRequireInstMultiMap;
|
|
|
|
/// First value is the operand that was transferred... second value is the
|
|
/// non-transferrable value in the same region as that value. The second value
|
|
/// is what is non-transferrable.
|
|
SmallVectorImpl<TransferredNonTransferrableInfo> &transferredNonTransferrable;
|
|
|
|
DiagnosticEvaluator(Partition &workingPartition,
|
|
RegionAnalysisFunctionInfo *info,
|
|
SmallFrozenMultiMap<Operand *, SILInstruction *, 8>
|
|
&transferOpToRequireInstMultiMap,
|
|
SmallVectorImpl<TransferredNonTransferrableInfo>
|
|
&transferredNonTransferrable)
|
|
: PartitionOpEvaluatorBaseImpl(workingPartition,
|
|
info->getOperandSetFactory()),
|
|
info(info),
|
|
transferOpToRequireInstMultiMap(transferOpToRequireInstMultiMap),
|
|
transferredNonTransferrable(transferredNonTransferrable) {}
|
|
|
|
void handleLocalUseAfterTransfer(const PartitionOp &partitionOp,
|
|
TrackableValueID transferredVal,
|
|
TransferringOperand transferringOp) const {
|
|
// Ignore this if we have a gep like instruction that is returning a
|
|
// sendable type and transferringOp was not set with closure
|
|
// capture.
|
|
if (auto *svi =
|
|
dyn_cast<SingleValueInstruction>(partitionOp.getSourceInst())) {
|
|
if (isa<TupleElementAddrInst, StructElementAddrInst>(svi) &&
|
|
!regionanalysisimpl::isNonSendableType(svi->getType(),
|
|
svi->getFunction())) {
|
|
bool isCapture = transferringOp.isClosureCaptured();
|
|
if (!isCapture) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto rep = info->getValueMap().getRepresentative(transferredVal);
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " Emitting Use After Transfer Error!\n"
|
|
<< " Transferring Inst: " << *transferringOp.getUser()
|
|
<< " Transferring Op Value: "
|
|
<< transferringOp.getOperand()->get()
|
|
<< " Require Inst: " << *partitionOp.getSourceInst()
|
|
<< " ID: %%" << transferredVal << "\n"
|
|
<< " Rep: " << *rep << " Transferring Op Num: "
|
|
<< transferringOp.getOperand()->getOperandNumber() << '\n');
|
|
transferOpToRequireInstMultiMap.insert(transferringOp.getOperand(),
|
|
partitionOp.getSourceInst());
|
|
}
|
|
|
|
void handleTransferNonTransferrable(const PartitionOp &partitionOp,
|
|
TrackableValueID transferredVal) const {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " Emitting TransferNonTransferrable Error!\n"
|
|
<< " ID: %%" << transferredVal << "\n"
|
|
<< " Rep: "
|
|
<< *info->getValueMap().getRepresentative(transferredVal));
|
|
auto *self = const_cast<DiagnosticEvaluator *>(this);
|
|
auto nonTransferrableValue =
|
|
info->getValueMap().getRepresentative(transferredVal);
|
|
self->transferredNonTransferrable.emplace_back(partitionOp.getSourceOp(),
|
|
nonTransferrableValue);
|
|
}
|
|
|
|
void handleTransferNonTransferrable(
|
|
const PartitionOp &partitionOp, TrackableValueID transferredVal,
|
|
TrackableValueID actualNonTransferrableValue) const {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " Emitting TransferNonTransferrable Error!\n"
|
|
<< " ID: %%" << transferredVal << "\n"
|
|
<< " Rep: "
|
|
<< *info->getValueMap().getRepresentative(transferredVal));
|
|
auto *self = const_cast<DiagnosticEvaluator *>(this);
|
|
// If we have a non-actor introducing fake representative value, just use
|
|
// the value that actually introduced the actor isolation.
|
|
if (auto nonTransferrableValue = info->getValueMap().maybeGetRepresentative(
|
|
actualNonTransferrableValue)) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " ActualTransfer: " << nonTransferrableValue);
|
|
self->transferredNonTransferrable.emplace_back(partitionOp.getSourceOp(),
|
|
nonTransferrableValue);
|
|
} else if (auto *nonTransferrableInst =
|
|
info->getValueMap().maybeGetActorIntroducingInst(
|
|
actualNonTransferrableValue)) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " ActualTransfer: " << *nonTransferrableInst);
|
|
self->transferredNonTransferrable.emplace_back(partitionOp.getSourceOp(),
|
|
nonTransferrableInst);
|
|
} else {
|
|
// Otherwise, just use the actual value.
|
|
//
|
|
// TODO: We are eventually going to want to be able to say that it is b/c
|
|
// of the actor isolated parameter. Maybe we should put in the actual
|
|
// region isolation info here.
|
|
self->transferredNonTransferrable.emplace_back(
|
|
partitionOp.getSourceOp(),
|
|
info->getValueMap().getRepresentative(transferredVal));
|
|
}
|
|
}
|
|
|
|
bool isActorDerived(Element element) const {
|
|
return info->getValueMap().getIsolationRegion(element).isActorIsolated();
|
|
}
|
|
|
|
bool isTaskIsolatedDerived(Element element) const {
|
|
return info->getValueMap().getIsolationRegion(element).isTaskIsolated();
|
|
}
|
|
|
|
ValueIsolationRegionInfo::Kind hasSpecialDerivation(Element element) const {
|
|
return info->getValueMap().getIsolationRegion(element).getKind();
|
|
}
|
|
|
|
ValueIsolationRegionInfo getIsolationRegionInfo(Element element) const {
|
|
return info->getValueMap().getIsolationRegion(element);
|
|
}
|
|
|
|
bool isClosureCaptured(Element element, Operand *op) const {
|
|
auto value = info->getValueMap().maybeGetRepresentative(element);
|
|
if (!value)
|
|
return false;
|
|
return info->isClosureCaptured(value, op);
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void TransferNonSendableImpl::runDiagnosticEvaluator() {
|
|
// Then for each block...
|
|
LLVM_DEBUG(llvm::dbgs() << "Walking blocks for diagnostics.\n");
|
|
for (auto [block, blockState] : regionInfo->getRange()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "|--> Block bb" << block.getDebugID() << "\n");
|
|
|
|
if (!blockState.getLiveness()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Dead block... skipping!\n");
|
|
continue;
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Entry Partition: ";
|
|
blockState.getEntryPartition().print(llvm::dbgs()));
|
|
|
|
// Grab its entry partition and setup an evaluator for the partition that
|
|
// has callbacks that emit diagnsotics...
|
|
Partition workingPartition = blockState.getEntryPartition();
|
|
DiagnosticEvaluator eval(workingPartition, regionInfo,
|
|
transferOpToRequireInstMultiMap,
|
|
transferredNonTransferrable);
|
|
|
|
// And then evaluate all of our partition ops on the entry partition.
|
|
for (auto &partitionOp : blockState.getPartitionOps()) {
|
|
eval.apply(partitionOp);
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Exit Partition: ";
|
|
workingPartition.print(llvm::dbgs()));
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Finished walking blocks for diagnostics.\n");
|
|
|
|
// Now that we have found all of our transferInsts/Requires emit errors.
|
|
transferOpToRequireInstMultiMap.setFrozen();
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Top Level Entrypoint
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Once we have reached a fixpoint, this routine runs over all blocks again
|
|
/// reporting any failures by applying our ops to the converged dataflow
|
|
/// state.
|
|
void TransferNonSendableImpl::emitDiagnostics() {
|
|
auto *function = regionInfo->getFunction();
|
|
LLVM_DEBUG(llvm::dbgs() << "Emitting diagnostics for function "
|
|
<< function->getName() << "\n");
|
|
|
|
runDiagnosticEvaluator();
|
|
emitTransferredNonTransferrableDiagnostics();
|
|
emitUseAfterTransferDiagnostics();
|
|
}
|
|
|
|
namespace {
|
|
|
|
class TransferNonSendable : public SILFunctionTransform {
|
|
void run() override {
|
|
SILFunction *function = getFunction();
|
|
|
|
auto *functionInfo = getAnalysis<RegionAnalysis>()->get(function);
|
|
if (!functionInfo->isSupportedFunction()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "===> SKIPPING UNSUPPORTED FUNCTION: "
|
|
<< function->getName() << '\n');
|
|
|
|
return;
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "===> PROCESSING: " << function->getName() << '\n');
|
|
|
|
TransferNonSendableImpl impl(functionInfo);
|
|
impl.emitDiagnostics();
|
|
}
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
SILTransform *swift::createTransferNonSendable() {
|
|
return new TransferNonSendable();
|
|
}
|