mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
We are creating/relying on a contract between the AST and SIL... that SILDeclRef should accurately describe the method/accessor that a class_method is from. By doing this we eliminate pattern matching on the AST which ties this code too tightly to the AST and makes it brittle in the face of AST changes. This also fixes an issue where we were not handling setters correctly. I am doing this now since it is natural to fix it along side fixing the ref_element_addr issue in the previous commit since they are effectively doing the same thing. rdar://153207557
1940 lines
70 KiB
C++
1940 lines
70 KiB
C++
//===--- SILIsolationInfo.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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/SILOptimizer/Utils/SILIsolationInfo.h"
|
|
|
|
#include "swift/AST/ASTWalker.h"
|
|
#include "swift/AST/ConformanceLookup.h"
|
|
#include "swift/AST/DistributedDecl.h"
|
|
#include "swift/AST/ExistentialLayout.h"
|
|
#include "swift/AST/Expr.h"
|
|
#include "swift/AST/PackConformance.h"
|
|
#include "swift/AST/ProtocolConformance.h"
|
|
#include "swift/SIL/AddressWalker.h"
|
|
#include "swift/SIL/ApplySite.h"
|
|
#include "swift/SIL/DynamicCasts.h"
|
|
#include "swift/SIL/InstructionUtils.h"
|
|
#include "swift/SIL/PatternMatch.h"
|
|
#include "swift/SIL/SILGlobalVariable.h"
|
|
#include "swift/SIL/Test.h"
|
|
#include "swift/SILOptimizer/Utils/VariableNameUtils.h"
|
|
|
|
using namespace swift;
|
|
using namespace swift::PatternMatch;
|
|
|
|
static std::optional<ActorIsolation>
|
|
getGlobalActorInitIsolation(SILFunction *fn) {
|
|
auto block = fn->begin();
|
|
|
|
// Make sure our function has a single block. We should always have a single
|
|
// block today. Return nullptr otherwise.
|
|
if (block == fn->end() || std::next(block) != fn->end())
|
|
return {};
|
|
|
|
GlobalAddrInst *gai = nullptr;
|
|
if (!match(cast<SILInstruction>(block->getTerminator()),
|
|
m_ReturnInst(m_AddressToPointerInst(m_GlobalAddrInst(gai)))))
|
|
return {};
|
|
|
|
auto *globalDecl = gai->getReferencedGlobal()->getDecl();
|
|
if (!globalDecl)
|
|
return {};
|
|
|
|
// See if our globalDecl is specifically guarded.
|
|
return getActorIsolation(globalDecl);
|
|
}
|
|
|
|
class DeclRefExprAnalysis {
|
|
DeclRefExpr *result = nullptr;
|
|
|
|
// Be greedy with the small size so we very rarely allocate.
|
|
SmallVector<Expr *, 8> lookThroughExprs;
|
|
|
|
public:
|
|
bool compute(Expr *expr);
|
|
|
|
DeclRefExpr *getResult() const {
|
|
assert(result && "Not computed?!");
|
|
return result;
|
|
}
|
|
|
|
ArrayRef<Expr *> getLookThroughExprs() const {
|
|
assert(result && "Not computed?!");
|
|
return lookThroughExprs;
|
|
}
|
|
|
|
void print(llvm::raw_ostream &os) const {
|
|
if (!result) {
|
|
os << "DeclRefExprAnalysis: None.";
|
|
return;
|
|
}
|
|
|
|
os << "DeclRefExprAnalysis:\n";
|
|
result->dump(os);
|
|
os << "\n";
|
|
if (lookThroughExprs.size()) {
|
|
os << "LookThroughExprs:\n";
|
|
for (auto *expr : lookThroughExprs) {
|
|
expr->dump(os, 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
|
|
|
|
bool hasNonisolatedUnsafe() const {
|
|
// See if our initial member_ref_expr is actor instance isolated.
|
|
for (auto *expr : lookThroughExprs) {
|
|
// We can skip load expr.
|
|
if (isa<LoadExpr>(expr))
|
|
continue;
|
|
|
|
if (auto *mri = dyn_cast<MemberRefExpr>(expr)) {
|
|
if (mri->hasDecl()) {
|
|
auto isolation = swift::getActorIsolation(mri->getDecl().getDecl());
|
|
if (isolation.isNonisolatedUnsafe())
|
|
return true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
bool DeclRefExprAnalysis::compute(Expr *expr) {
|
|
struct LocalWalker final : ASTWalker {
|
|
DeclRefExprAnalysis &parentAnalysis;
|
|
|
|
LocalWalker(DeclRefExprAnalysis &parentAnalysis)
|
|
: parentAnalysis(parentAnalysis) {}
|
|
|
|
PreWalkResult<Expr *> walkToExprPre(Expr *expr) override {
|
|
assert(!parentAnalysis.result && "Shouldn't have a result yet");
|
|
|
|
if (auto *dre = dyn_cast<DeclRefExpr>(expr)) {
|
|
parentAnalysis.result = dre;
|
|
return Action::Stop();
|
|
}
|
|
|
|
if (isa<CoerceExpr, MemberRefExpr, ImplicitConversionExpr, IdentityExpr>(
|
|
expr)) {
|
|
parentAnalysis.lookThroughExprs.push_back(expr);
|
|
return Action::Continue(expr);
|
|
}
|
|
|
|
return Action::Stop();
|
|
}
|
|
};
|
|
|
|
LocalWalker walker(*this);
|
|
|
|
if (auto *ae = dyn_cast<AssignExpr>(expr)) {
|
|
ae->getSrc()->walk(walker);
|
|
} else {
|
|
expr->walk(walker);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static SILIsolationInfo
|
|
inferIsolationInfoForTempAllocStack(AllocStackInst *asi) {
|
|
// We want to search for an alloc_stack that is not from a VarDecl and that is
|
|
// initially isolated along all paths to the same actor isolation. If they
|
|
// differ, then we emit a we do not understand error.
|
|
struct AddressWalkerState {
|
|
AllocStackInst *asi = nullptr;
|
|
SmallVector<Operand *, 8> indirectResultUses;
|
|
llvm::SmallSetVector<SILInstruction *, 8> writes;
|
|
Operand *sameBlockIndirectResultUses = nullptr;
|
|
};
|
|
|
|
struct AddressWalker final : TransitiveAddressWalker<AddressWalker> {
|
|
AddressWalkerState &state;
|
|
|
|
AddressWalker(AddressWalkerState &state) : state(state) {
|
|
assert(state.asi);
|
|
}
|
|
|
|
bool visitUse(Operand *use) {
|
|
// If we do not write to memory, then it is harmless.
|
|
if (!use->getUser()->mayWriteToMemory())
|
|
return true;
|
|
|
|
if (auto fas = FullApplySite::isa(use->getUser())) {
|
|
if (fas.isIndirectResultOperand(*use)) {
|
|
// If our indirect result use is in the same block...
|
|
auto *parentBlock = state.asi->getParent();
|
|
if (fas.getParent() == parentBlock) {
|
|
// If we haven't seen any indirect result use yet... just cache it
|
|
// and return true.
|
|
if (!state.sameBlockIndirectResultUses) {
|
|
state.sameBlockIndirectResultUses = use;
|
|
return true;
|
|
}
|
|
|
|
// If by walking from the alloc stack to the full apply site, we do
|
|
// not see the current sameBlockIndirectResultUses, we have a new
|
|
// newest use.
|
|
if (llvm::none_of(
|
|
llvm::make_range(state.asi->getIterator(),
|
|
fas->getIterator()),
|
|
[&](const SILInstruction &inst) {
|
|
return &inst ==
|
|
state.sameBlockIndirectResultUses->getUser();
|
|
})) {
|
|
state.sameBlockIndirectResultUses = use;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// If not, just stash it into the non-same block indirect result use
|
|
// array.
|
|
state.indirectResultUses.push_back(use);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
state.writes.insert(use->getUser());
|
|
return true;
|
|
}
|
|
};
|
|
|
|
AddressWalkerState state;
|
|
state.asi = asi;
|
|
AddressWalker walker(state);
|
|
|
|
// If we fail to walk, emit an unknown patten error.
|
|
//
|
|
// FIXME: check AddressUseKind::NonEscaping != walk().
|
|
if (AddressUseKind::Unknown == std::move(walker).walk(asi)) {
|
|
return SILIsolationInfo();
|
|
}
|
|
|
|
// If we do not have any indirect result uses... we can just assign fresh.
|
|
if (!state.sameBlockIndirectResultUses && state.indirectResultUses.empty())
|
|
return SILIsolationInfo::getDisconnected(false /*isUnsafeNonIsolated*/);
|
|
|
|
// Otherwise, lets see if we had a same block indirect result.
|
|
if (state.sameBlockIndirectResultUses) {
|
|
// Check if this indirect result has a sending result. In such a case, we
|
|
// always return disconnected.
|
|
if (auto fas =
|
|
FullApplySite::isa(state.sameBlockIndirectResultUses->getUser())) {
|
|
if (fas.getSubstCalleeType()->hasSendingResult())
|
|
return SILIsolationInfo::getDisconnected(
|
|
false /*is unsafe non isolated*/);
|
|
}
|
|
|
|
// If we do not have any writes in between the alloc stack and the
|
|
// initializer, then we have a good target. Otherwise, we just return
|
|
// AssignFresh.
|
|
if (llvm::none_of(
|
|
llvm::make_range(
|
|
asi->getIterator(),
|
|
state.sameBlockIndirectResultUses->getUser()->getIterator()),
|
|
[&](SILInstruction &inst) { return state.writes.count(&inst); })) {
|
|
auto isolationInfo =
|
|
SILIsolationInfo::get(state.sameBlockIndirectResultUses->getUser());
|
|
if (isolationInfo) {
|
|
return isolationInfo;
|
|
}
|
|
}
|
|
|
|
// If we did not find an isolation info, just do a normal assign fresh.
|
|
return SILIsolationInfo::getDisconnected(false /*is unsafe non isolated*/);
|
|
}
|
|
|
|
// Check if any of our writes are within the first block. This would
|
|
// automatically stop our search and we should assign fresh. Since we are
|
|
// going over the writes here, also setup a writeBlocks set.
|
|
auto *defBlock = asi->getParent();
|
|
BasicBlockSet writeBlocks(defBlock->getParent());
|
|
for (auto *write : state.writes) {
|
|
if (write->getParent() == defBlock)
|
|
return SILIsolationInfo::getDisconnected(false /*unsafe non isolated*/);
|
|
writeBlocks.insert(write->getParent());
|
|
}
|
|
|
|
// Ok, at this point we know that we do not have any indirect result uses in
|
|
// the def block and also we do not have any writes in that initial
|
|
// block. This sets us up for our global analysis. Our plan is as follows:
|
|
//
|
|
// 1. We are going to create a set of writeBlocks and a map from SILBasicBlock
|
|
// -> first indirect result block if there isn't a write before it.
|
|
//
|
|
// 2. We walk from our def block until we reach the first indirect result
|
|
// block. We stop processing successor if we find a write block successor that
|
|
// is not also an indirect result block. This makes sense since we earlier
|
|
// required that any notates indirect result block do not have any writes in
|
|
// between the indirect result and the beginning of the block.
|
|
llvm::SmallDenseMap<SILBasicBlock *, Operand *, 2> blockToOperandMap;
|
|
for (auto *use : state.indirectResultUses) {
|
|
// If our indirect result use has a write before it in the block, do not
|
|
// store it. It cannot be our indirect result initializer.
|
|
if (writeBlocks.contains(use->getParentBlock()) &&
|
|
llvm::any_of(
|
|
use->getParentBlock()->getRangeEndingAtInst(use->getUser()),
|
|
[&](SILInstruction &inst) { return state.writes.contains(&inst); }))
|
|
continue;
|
|
|
|
// Ok, we now know that there aren't any writes before us in the block. Now
|
|
// try to insert.
|
|
auto iter = blockToOperandMap.try_emplace(use->getParentBlock(), use);
|
|
|
|
// If we actually inserted, then we are done.
|
|
if (iter.second) {
|
|
continue;
|
|
}
|
|
|
|
// Otherwise, if we are before the current value, set us to be the value
|
|
// instead.
|
|
if (llvm::none_of(
|
|
use->getParentBlock()->getRangeEndingAtInst(use->getUser()),
|
|
[&](const SILInstruction &inst) {
|
|
return &inst == iter.first->second->getUser();
|
|
})) {
|
|
iter.first->getSecond() = use;
|
|
}
|
|
}
|
|
|
|
// Ok, we now have our data all setup.
|
|
BasicBlockWorklist worklist(asi->getFunction());
|
|
for (auto *succBlock : asi->getParentBlock()->getSuccessorBlocks()) {
|
|
worklist.pushIfNotVisited(succBlock);
|
|
}
|
|
|
|
Operand *targetOperand = nullptr;
|
|
while (auto *next = worklist.pop()) {
|
|
// First check if this is one of our target blocks.
|
|
auto iter = blockToOperandMap.find(next);
|
|
|
|
// If this is our target blocks...
|
|
if (iter != blockToOperandMap.end()) {
|
|
// If we already have an assigned target block, make sure this is the same
|
|
// one. If it is, just continue. Otherwise, something happened we do not
|
|
// understand... assign fresh.
|
|
if (!targetOperand) {
|
|
targetOperand = iter->second;
|
|
continue;
|
|
}
|
|
|
|
if (targetOperand->getParentBlock() == iter->first) {
|
|
continue;
|
|
}
|
|
|
|
return SILIsolationInfo::getDisconnected(
|
|
false /*is unsafe non isolated*/);
|
|
}
|
|
|
|
// Otherwise, see if this block is a write block. If so, we have a path to a
|
|
// write block that does not go through one of our blockToOperandMap
|
|
// blocks... return assign fresh.
|
|
if (writeBlocks.contains(next))
|
|
return SILIsolationInfo::getDisconnected(
|
|
false /*is unsafe non isolated*/);
|
|
|
|
// Otherwise, visit this blocks successors if we have not yet visited them.
|
|
for (auto *succBlock : next->getSuccessorBlocks()) {
|
|
worklist.pushIfNotVisited(succBlock);
|
|
}
|
|
}
|
|
|
|
// At this point, we know that we have a single indirect result use that
|
|
// dominates all writes and other indirect result uses. We can say that our
|
|
// alloc_stack temporary is that indirect result use's isolation.
|
|
if (auto fas = FullApplySite::isa(targetOperand->getUser())) {
|
|
if (fas.getSubstCalleeType()->hasSendingResult())
|
|
return SILIsolationInfo::getDisconnected(
|
|
false /*is unsafe non isolated*/);
|
|
}
|
|
return SILIsolationInfo::get(targetOperand->getUser());
|
|
}
|
|
|
|
static SILValue lookThroughNonVarDeclOwnershipInsts(SILValue v) {
|
|
while (true) {
|
|
if (auto *svi = dyn_cast<SingleValueInstruction>(v)) {
|
|
if (isa<CopyValueInst>(svi)) {
|
|
v = svi->getOperand(0);
|
|
continue;
|
|
}
|
|
|
|
if (auto *bbi = dyn_cast<BeginBorrowInst>(v)) {
|
|
if (!bbi->isFromVarDecl()) {
|
|
v = bbi->getOperand();
|
|
continue;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
if (auto *mvi = dyn_cast<MoveValueInst>(v)) {
|
|
if (!mvi->isFromVarDecl()) {
|
|
v = mvi->getOperand();
|
|
continue;
|
|
}
|
|
|
|
return v;
|
|
}
|
|
}
|
|
|
|
return v;
|
|
}
|
|
}
|
|
|
|
/// See if \p pai has at least one nonisolated(unsafe) capture and that all
|
|
/// captures are either nonisolated(unsafe) or sendable.
|
|
static bool isPartialApplyNonisolatedUnsafe(PartialApplyInst *pai) {
|
|
bool foundOneNonIsolatedUnsafe = false;
|
|
for (auto &op : pai->getArgumentOperands()) {
|
|
if (SILIsolationInfo::isSendableType(op.get()))
|
|
continue;
|
|
|
|
// Normally we would not look through copy_value, begin_borrow, or
|
|
// move_value since this is meant to find the inherent isolation of a
|
|
// specific element. But since we are checking for captures rather
|
|
// than the actual value itself (just for unsafe nonisolated
|
|
// purposes), it is ok.
|
|
//
|
|
// E.x.: As an example of something we want to prevent, consider an
|
|
// invocation of a nonisolated function that is a parameter to an
|
|
// @MainActor function. That means from a region isolation
|
|
// perspective, the function parameter is in the MainActor region, but
|
|
// the actual function itself is not MainActor isolated, since the
|
|
// function will not hop onto the main actor.
|
|
//
|
|
// TODO: We should use some of the shared infrastructure to find the
|
|
// underlying value of op.get(). This is conservatively correct for
|
|
// now.
|
|
auto value = lookThroughNonVarDeclOwnershipInsts(op.get());
|
|
if (!SILIsolationInfo::get(value).isUnsafeNonIsolated())
|
|
return false;
|
|
foundOneNonIsolatedUnsafe = true;
|
|
}
|
|
|
|
return foundOneNonIsolatedUnsafe;
|
|
}
|
|
|
|
/// Return the SILIsolationInfo for a class field for a ref_element_addr or
|
|
/// class_method. Methods that are direct should get their isolation information
|
|
/// from the static function rather than from this function.
|
|
///
|
|
/// \arg queriedValue the actual value that SILIsolationInfo::get was called
|
|
/// upon. This is used for IsolationHistory.
|
|
///
|
|
/// \arg classValue this is the actual underlying class value that we are
|
|
/// extracting a field out of. As an example this is the base passed to
|
|
/// ref_element_addr or class_method. This /can/ be a metatype potentially in
|
|
/// the case of class 'class' methods and computed properties.
|
|
///
|
|
/// \arg field the actual AST field that we discovered we are querying. This
|
|
/// could be the field of the ref_element_addr or an accessor decl extracted
|
|
/// from a SILDeclRef of a class_method.
|
|
static SILIsolationInfo computeIsolationForClassField(SILValue queriedValue,
|
|
SILValue classValue,
|
|
ValueDecl *field) {
|
|
// First look for explicit isolation on the field itself. These always
|
|
// override what is on the class.
|
|
auto varIsolation = swift::getActorIsolation(field);
|
|
|
|
// If we have an explicitly global actor isolated field, then we must prefer
|
|
// that isolation since it takes precedence over any isolation directly on the
|
|
// underlying class type.
|
|
if (varIsolation.isGlobalActor()) {
|
|
assert(!varIsolation.isNonisolatedUnsafe() &&
|
|
"Cannot apply both nonisolated(unsafe) and a global actor attribute "
|
|
"to the same declaration");
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
queriedValue, varIsolation.getGlobalActor());
|
|
}
|
|
|
|
// Then check if our field is explicitly nonisolated (not
|
|
// nonisolated(unsafe)). In such a case, we want to return disconnected since
|
|
// we are overriding the isolation of the actual nominal type. If we have
|
|
// nonisolated(unsafe), we want to respect the isolation of the nominal type
|
|
// since we just want to squelch the error but still have it be isolated in
|
|
// its normal way. This provides more information since we know what the
|
|
// underlying isolation /would/ have been.
|
|
if (varIsolation.isNonisolated() && !varIsolation.isNonisolatedUnsafe())
|
|
return SILIsolationInfo::getDisconnected(false /*nonisolated unsafe*/);
|
|
|
|
// Ok, we know that we do not have any overriding isolation from the var
|
|
// itself... so now we should use isolation from the underlying nominal type.
|
|
|
|
// First see if we have an actor instance value from an isolated
|
|
// SILFunctionArgument.
|
|
if (auto instance = ActorInstance::getForValue(classValue)) {
|
|
if (auto *fArg = llvm::dyn_cast_or_null<SILFunctionArgument>(
|
|
instance.maybeGetValue())) {
|
|
if (auto info =
|
|
SILIsolationInfo::getActorInstanceIsolated(queriedValue, fArg)) {
|
|
return info.withUnsafeNonIsolated(varIsolation.isNonisolatedUnsafe());
|
|
}
|
|
}
|
|
}
|
|
|
|
// First find out if our classValue is a nominal type and exit early...
|
|
if (auto *nomDecl = classValue->getType().getNominalOrBoundGenericNominal()) {
|
|
if (auto isolation = swift::getActorIsolation(nomDecl);
|
|
isolation && isolation.isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
queriedValue, isolation.getGlobalActor())
|
|
.withUnsafeNonIsolated(varIsolation.isNonisolatedUnsafe());
|
|
}
|
|
|
|
if (nomDecl->isAnyActor())
|
|
return SILIsolationInfo::getActorInstanceIsolated(queriedValue,
|
|
classValue, nomDecl)
|
|
.withUnsafeNonIsolated(varIsolation.isNonisolatedUnsafe());
|
|
}
|
|
|
|
// If we have a metatype...
|
|
if (classValue->getType().isMetatype()) {
|
|
// And we can a class nominal decl...
|
|
if (auto *nomDecl =
|
|
classValue->getType()
|
|
.getLoweredInstanceTypeOfMetatype(classValue->getFunction())
|
|
.getNominalOrBoundGenericNominal()) {
|
|
|
|
// See if the nominal decl is global actor isolated. In such a case, we
|
|
// know that the metatype is also actor isolated.
|
|
if (auto isolation = swift::getActorIsolation(nomDecl);
|
|
isolation && isolation.isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
queriedValue, isolation.getGlobalActor())
|
|
.withUnsafeNonIsolated(varIsolation.isNonisolatedUnsafe());
|
|
}
|
|
|
|
// Then finally check if we have an actor instance and we are getting an
|
|
// async allocating initializer for it.
|
|
if (nomDecl->isAnyActor()) {
|
|
if (auto *constructorDecl = dyn_cast<ConstructorDecl>(field);
|
|
constructorDecl && constructorDecl->isAsync()) {
|
|
return SILIsolationInfo::getActorInstanceIsolated(
|
|
classValue, ActorInstance::getForActorAsyncAllocatingInit(),
|
|
nomDecl);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return SILIsolationInfo::getDisconnected(varIsolation.isNonisolatedUnsafe());
|
|
}
|
|
|
|
SILIsolationInfo SILIsolationInfo::get(SILInstruction *inst) {
|
|
if (auto fas = FullApplySite::isa(inst)) {
|
|
// Before we do anything, see if we have a sending result. In such a case,
|
|
// our full apply site result must be disconnected.
|
|
//
|
|
// NOTE: This handles the direct case of a sending result. The indirect case
|
|
// is handled above.
|
|
if (fas.getSubstCalleeType()->hasSendingResult())
|
|
return SILIsolationInfo::getDisconnected(
|
|
false /*is unsafe non isolated*/);
|
|
|
|
// Check if we have SIL based "faked" isolation crossings that we use for
|
|
// testing purposes.
|
|
//
|
|
// NOTE: Please do not use getWithIsolationCrossing in more places! We only
|
|
// want to use it here!
|
|
if (auto crossing = fas.getIsolationCrossing()) {
|
|
if (auto info = SILIsolationInfo::getWithIsolationCrossing(*crossing))
|
|
return info;
|
|
}
|
|
|
|
// See if our function apply site has an implicit isolated parameter. In
|
|
// such a case, we know that we have a caller inheriting isolated
|
|
// function. Return that this has disconnected isolation.
|
|
//
|
|
// DISCUSSION: The reason why we are doing this is that we already know that
|
|
// the AST is not going to label this as an isolation crossing point so we
|
|
// will only perform a merge. We want to just perform an isolation merge
|
|
// without adding additional isolation info. Otherwise, a nonisolated
|
|
// function that
|
|
if (auto paramInfo = fas.getSubstCalleeType()->maybeGetIsolatedParameter();
|
|
paramInfo && paramInfo->hasOption(SILParameterInfo::ImplicitLeading) &&
|
|
paramInfo->hasOption(SILParameterInfo::Isolated)) {
|
|
return SILIsolationInfo::getDisconnected(false /*unsafe nonisolated*/);
|
|
}
|
|
|
|
if (auto *isolatedOp = fas.getIsolatedArgumentOperandOrNullPtr()) {
|
|
// First look through ActorInstance agnostic values so we can find the
|
|
// type of the actual underlying actor (e.x.: copy_value,
|
|
// init_existential_ref, begin_borrow, etc).
|
|
auto actualIsolatedValue =
|
|
ActorInstance::lookThroughInsts(isolatedOp->get());
|
|
|
|
// First see if we have a .none enum inst. In such a case, we are actually
|
|
// on the nonisolated global queue.
|
|
if (auto *ei = dyn_cast<EnumInst>(actualIsolatedValue)) {
|
|
if (ei->getElement()->getParentEnum()->isOptionalDecl() &&
|
|
!ei->hasOperand()) {
|
|
// In this case, we have a .none so we are attempting to use the
|
|
// global queue. This means that the isolation effect of the
|
|
// function is disconnected since we are treating the function as
|
|
// nonisolated.
|
|
return SILIsolationInfo::getDisconnected(false);
|
|
}
|
|
}
|
|
|
|
// Then using that value, grab the AST type from the actual isolated
|
|
// value.
|
|
CanType selfASTType = actualIsolatedValue->getType().getASTType();
|
|
|
|
// Then look through optional types since in cases like where we have a
|
|
// function argument that is an Optional actor... like an optional actor
|
|
// returned from a function, we still need to be able to lookup the actor
|
|
// as being the underlying type.
|
|
selfASTType =
|
|
selfASTType->lookThroughAllOptionalTypes()->getCanonicalType();
|
|
if (auto *nomDecl = selfASTType->getAnyActor()) {
|
|
// Then see if we have a global actor. This pattern matches the output
|
|
// for doing things like GlobalActor.shared.
|
|
if (nomDecl->isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(SILValue(), selfASTType);
|
|
}
|
|
|
|
if (auto *fArg = dyn_cast<SILFunctionArgument>(actualIsolatedValue)) {
|
|
if (auto info =
|
|
SILIsolationInfo::getActorInstanceIsolated(fArg, fArg))
|
|
return info;
|
|
}
|
|
|
|
// TODO: We really should be doing this based off of an Operand. Then
|
|
// we would get the SILValue() for the first element. Today this can
|
|
// only mess up isolation history.
|
|
return SILIsolationInfo::getActorInstanceIsolated(
|
|
SILValue(), actualIsolatedValue, nomDecl);
|
|
}
|
|
}
|
|
|
|
// See if we can infer isolation from our callee.
|
|
if (auto isolationInfo = get(fas.getCallee())) {
|
|
return isolationInfo;
|
|
}
|
|
}
|
|
|
|
if (auto *pai = dyn_cast<PartialApplyInst>(inst)) {
|
|
// Check if we have any captures and if the isolation info for all captures
|
|
// are nonisolated(unsafe) or Sendable. In such a case, we consider the
|
|
// partial_apply to be nonisolated(unsafe). We purposely do not do this if
|
|
// the partial_apply does not have any parameters just out of paranoia... we
|
|
// shouldn't have such a partial_apply emitted by SILGen (it should use a
|
|
// thin to thick function or something like that)... but in that case since
|
|
// we do not have any nonisolated(unsafe), it doesn't make sense to
|
|
// propagate nonisolated(unsafe).
|
|
bool partialApplyIsNonIsolatedUnsafe = isPartialApplyNonisolatedUnsafe(pai);
|
|
|
|
if (auto *ace = pai->getLoc().getAsASTNode<AbstractClosureExpr>()) {
|
|
auto actorIsolation = ace->getActorIsolation();
|
|
|
|
if (actorIsolation.isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
pai, actorIsolation.getGlobalActor())
|
|
.withUnsafeNonIsolated(partialApplyIsNonIsolatedUnsafe);
|
|
}
|
|
|
|
if (actorIsolation.isActorInstanceIsolated()) {
|
|
ApplySite as(pai);
|
|
SILValue actorInstance;
|
|
for (auto &op : as.getArgumentOperands()) {
|
|
if (as.getArgumentParameterInfo(op).hasOption(
|
|
SILParameterInfo::Isolated)) {
|
|
actorInstance = op.get();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Ok, we found an actor instance. Look for our actual isolated value.
|
|
if (actorInstance) {
|
|
if (auto actualIsolatedValue =
|
|
ActorInstance::getForValue(actorInstance)) {
|
|
// See if we have a function parameter. In such a case, we need to
|
|
// use the right parameter and the var decl.
|
|
if (auto *fArg = dyn_cast<SILFunctionArgument>(
|
|
actualIsolatedValue.getValue())) {
|
|
if (auto info =
|
|
SILIsolationInfo::getActorInstanceIsolated(pai, fArg)
|
|
.withUnsafeNonIsolated(
|
|
partialApplyIsNonIsolatedUnsafe))
|
|
return info;
|
|
}
|
|
}
|
|
|
|
return SILIsolationInfo::getActorInstanceIsolated(
|
|
pai, actorInstance, actorIsolation.getActor())
|
|
.withUnsafeNonIsolated(partialApplyIsNonIsolatedUnsafe);
|
|
}
|
|
|
|
// For now, if we do not have an actor instance, just create an actor
|
|
// instance isolated without an actor instance.
|
|
//
|
|
// If we do not have an actor instance, that means that we have a
|
|
// partial apply for which the isolated parameter was not closed over
|
|
// and is an actual argument that we pass in. This means that the
|
|
// partial apply is actually flow sensitive in terms of which specific
|
|
// actor instance we are isolated to.
|
|
//
|
|
// TODO: How do we want to resolve this.
|
|
return SILIsolationInfo::getPartialApplyActorInstanceIsolated(
|
|
pai, actorIsolation.getActor())
|
|
.withUnsafeNonIsolated(partialApplyIsNonIsolatedUnsafe);
|
|
}
|
|
|
|
assert(actorIsolation.getKind() != ActorIsolation::Erased &&
|
|
"Implement this!");
|
|
}
|
|
|
|
if (partialApplyIsNonIsolatedUnsafe)
|
|
return SILIsolationInfo::getDisconnected(partialApplyIsNonIsolatedUnsafe);
|
|
}
|
|
|
|
// See if the memory base is a ref_element_addr from an address.
|
|
if (auto *rei = dyn_cast<RefElementAddrInst>(inst)) {
|
|
return computeIsolationForClassField(rei, rei->getOperand(),
|
|
rei->getField());
|
|
}
|
|
|
|
// Check if we have a global_addr inst.
|
|
if (auto *ga = dyn_cast<GlobalAddrInst>(inst)) {
|
|
if (auto *global = ga->getReferencedGlobal()) {
|
|
if (auto *globalDecl = global->getDecl()) {
|
|
auto isolation = swift::getActorIsolation(globalDecl);
|
|
if (isolation.isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
ga, isolation.getGlobalActor());
|
|
}
|
|
|
|
if (isolation.isNonisolatedUnsafe()) {
|
|
return SILIsolationInfo::getDisconnected(
|
|
true /*is nonisolated(unsafe)*/);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Treat function ref as either actor isolated or sendable.
|
|
if (auto *fri = dyn_cast<FunctionRefInst>(inst)) {
|
|
if (auto optIsolation = fri->getReferencedFunction()->getActorIsolation()) {
|
|
auto isolation = *optIsolation;
|
|
|
|
// First check if we are actor isolated at the AST level... if we are,
|
|
// then create the relevant actor isolated.
|
|
if (isolation.isActorIsolated()) {
|
|
if (isolation.isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
fri, isolation.getGlobalActor());
|
|
}
|
|
|
|
// TODO: We need to be able to support flow sensitive actor instances
|
|
// like we do for partial apply. Until we do so, just store SILValue()
|
|
// for this. This could cause a problem if we can construct a function
|
|
// ref and invoke it with two different actor instances of the same type
|
|
// and pass in the same parameters to both. We should error and we would
|
|
// not with this impl since we could not distinguish the two.
|
|
if (isolation.getKind() == ActorIsolation::ActorInstance) {
|
|
return SILIsolationInfo::getFlowSensitiveActorIsolated(fri,
|
|
isolation);
|
|
}
|
|
|
|
assert(isolation.getKind() != ActorIsolation::Erased &&
|
|
"Implement this!");
|
|
}
|
|
|
|
// Then check if we have something that is nonisolated unsafe.
|
|
if (isolation.isNonisolatedUnsafe()) {
|
|
// First check if our function_ref is a method of a global actor
|
|
// isolated type. In such a case, we create a global actor isolated
|
|
// nonisolated(unsafe) so that if we assign the value to another
|
|
// variable, the variable still says that it is the appropriate global
|
|
// actor isolated thing.
|
|
//
|
|
// E.x.:
|
|
//
|
|
// @MainActor
|
|
// struct X { nonisolated(unsafe) var x: NonSendableThing { ... } }
|
|
//
|
|
// We want X.x to be safe to use... but to have that 'z' in the
|
|
// following is considered MainActor isolated.
|
|
//
|
|
// let z = X.x
|
|
//
|
|
auto *func = fri->getReferencedFunction();
|
|
auto funcType = func->getLoweredFunctionType();
|
|
if (funcType->hasSelfParam()) {
|
|
auto selfParam = funcType->getSelfInstanceType(
|
|
fri->getModule(), func->getTypeExpansionContext());
|
|
if (auto *nomDecl = selfParam->getNominalOrBoundGenericNominal()) {
|
|
auto nomDeclIsolation = swift::getActorIsolation(nomDecl);
|
|
if (nomDeclIsolation.isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
fri, nomDeclIsolation.getGlobalActor())
|
|
.withUnsafeNonIsolated(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise, lets look at the AST and see if our function ref is from an
|
|
// autoclosure.
|
|
if (auto *autoclosure = fri->getLoc().getAsASTNode<AutoClosureExpr>()) {
|
|
if (auto *funcType = autoclosure->getType()->getAs<AnyFunctionType>()) {
|
|
if (funcType->hasGlobalActor()) {
|
|
if (funcType->hasGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
fri, funcType->getGlobalActor());
|
|
}
|
|
}
|
|
|
|
if (auto *resultFType =
|
|
funcType->getResult()->getAs<AnyFunctionType>()) {
|
|
if (resultFType->hasGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
fri, resultFType->getGlobalActor());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auto *cmi = dyn_cast<ClassMethodInst>(inst)) {
|
|
auto base = cmi->getOperand();
|
|
auto member = cmi->getMember();
|
|
|
|
// First see if we can use our SILDeclRef to infer isolation.
|
|
if (auto *accessor = member.getAccessorDecl()) {
|
|
return computeIsolationForClassField(cmi, base, accessor);
|
|
}
|
|
|
|
if (auto *funcDecl = member.getAbstractFunctionDecl()) {
|
|
return computeIsolationForClassField(cmi, base, funcDecl);
|
|
}
|
|
|
|
llvm_unreachable("Unsupported?!");
|
|
}
|
|
|
|
// See if we have a struct_extract from a global-actor-isolated type.
|
|
if (auto *sei = dyn_cast<StructExtractInst>(inst)) {
|
|
auto varIsolation = swift::getActorIsolation(sei->getField());
|
|
|
|
// If our var is global actor isolated, then we override the isolation of
|
|
// whatever our struct was with a specific isolation on the struct
|
|
// itself. We should use that instead.
|
|
if (varIsolation.isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
sei, varIsolation.getGlobalActor())
|
|
.withUnsafeNonIsolated(varIsolation.isNonisolatedUnsafe());
|
|
}
|
|
|
|
if (auto isolation =
|
|
SILIsolationInfo::getGlobalActorIsolated(sei, sei->getStructDecl()))
|
|
return isolation.withUnsafeNonIsolated(
|
|
varIsolation.isNonisolatedUnsafe());
|
|
return SILIsolationInfo::getDisconnected(
|
|
varIsolation.isNonisolatedUnsafe());
|
|
}
|
|
|
|
if (auto *seai = dyn_cast<StructElementAddrInst>(inst)) {
|
|
auto varIsolation = swift::getActorIsolation(seai->getField());
|
|
|
|
// If our var is global actor isolated, then we override the isolation of
|
|
// whatever our struct was with a specific isolation on the struct
|
|
// itself. We should use that instead.
|
|
if (varIsolation.isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
seai, varIsolation.getGlobalActor())
|
|
.withUnsafeNonIsolated(varIsolation.isNonisolatedUnsafe());
|
|
}
|
|
|
|
if (auto isolation = SILIsolationInfo::getGlobalActorIsolated(
|
|
seai, seai->getStructDecl()))
|
|
return isolation.withUnsafeNonIsolated(
|
|
varIsolation.isNonisolatedUnsafe());
|
|
return SILIsolationInfo::getDisconnected(
|
|
varIsolation.isNonisolatedUnsafe());
|
|
}
|
|
|
|
// See if we have an unchecked_enum_data from a global-actor-isolated type.
|
|
if (auto *uedi = dyn_cast<UncheckedEnumDataInst>(inst)) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(uedi, uedi->getEnumDecl());
|
|
}
|
|
|
|
// See if we have an unchecked_enum_data from a global-actor-isolated type.
|
|
if (auto *utedi = dyn_cast<UncheckedTakeEnumDataAddrInst>(inst)) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(utedi,
|
|
utedi->getEnumDecl());
|
|
}
|
|
|
|
// Check if we have an unsafeMutableAddressor from a global actor, mark the
|
|
// returned value as being actor derived.
|
|
if (auto applySite = dyn_cast<ApplyInst>(inst)) {
|
|
if (auto *calleeFunction = applySite->getCalleeFunction()) {
|
|
if (calleeFunction->isGlobalInit()) {
|
|
auto isolation = getGlobalActorInitIsolation(calleeFunction);
|
|
if (isolation && isolation->isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
applySite, isolation->getGlobalActor());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// See if we have a convert function from a Sendable actor-isolated function,
|
|
// we want to treat the result of the convert function as being actor isolated
|
|
// so that we cannot escape the value.
|
|
//
|
|
// NOTE: At this point, we already know that cfi's result is not sendable,
|
|
// since we would have exited above already.
|
|
if (auto *cfi = dyn_cast<ConvertFunctionInst>(inst)) {
|
|
SILValue operand = cfi->getOperand();
|
|
if (operand->getType().getAs<SILFunctionType>()->isSendable()) {
|
|
SILValue newValue = operand;
|
|
do {
|
|
operand = newValue;
|
|
|
|
newValue = lookThroughOwnershipInsts(operand);
|
|
if (auto *ttfi = dyn_cast<ThinToThickFunctionInst>(newValue)) {
|
|
newValue = ttfi->getOperand();
|
|
}
|
|
|
|
if (auto *cfi = dyn_cast<ConvertFunctionInst>(newValue)) {
|
|
newValue = cfi->getOperand();
|
|
}
|
|
|
|
if (auto *pai = dyn_cast<PartialApplyInst>(newValue)) {
|
|
newValue = pai->getCallee();
|
|
}
|
|
} while (newValue != operand);
|
|
|
|
if (auto *ai = dyn_cast<ApplyInst>(operand)) {
|
|
if (auto *callExpr = ai->getLoc().getAsASTNode<ApplyExpr>()) {
|
|
if (auto *callType = callExpr->getType()->getAs<AnyFunctionType>()) {
|
|
if (callType->hasGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
ai, callType->getGlobalActor());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auto *fri = dyn_cast<FunctionRefInst>(operand)) {
|
|
if (auto isolation = SILIsolationInfo::get(fri)) {
|
|
return isolation;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auto *asi = dyn_cast<AllocStackInst>(inst)) {
|
|
if (asi->isFromVarDecl()) {
|
|
if (auto *varDecl = asi->getLoc().getAsASTNode<VarDecl>()) {
|
|
auto isolation = swift::getActorIsolation(varDecl);
|
|
if (isolation.getKind() == ActorIsolation::NonisolatedUnsafe) {
|
|
return SILIsolationInfo::getDisconnected(
|
|
true /*is nonisolated(unsafe)*/);
|
|
}
|
|
}
|
|
} else {
|
|
// Ok, we have a temporary. If it is non-Sendable...
|
|
if (SILIsolationInfo::isNonSendableType(asi)) {
|
|
if (auto isolation = inferIsolationInfoForTempAllocStack(asi))
|
|
return isolation;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auto *mvi = dyn_cast<MoveValueInst>(inst)) {
|
|
if (mvi->isFromVarDecl()) {
|
|
if (auto *debugInfo = getSingleDebugUse(mvi)) {
|
|
if (auto *dbg = dyn_cast<DebugValueInst>(debugInfo->getUser())) {
|
|
if (auto *varDecl = dbg->getLoc().getAsASTNode<VarDecl>()) {
|
|
auto isolation = swift::getActorIsolation(varDecl);
|
|
if (isolation.getKind() == ActorIsolation::NonisolatedUnsafe) {
|
|
return SILIsolationInfo::getDisconnected(
|
|
true /*is nonisolated(unsafe)*/);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Consider non-Sendable metatypes to be task-isolated, so they cannot cross
|
|
/// into another isolation domain.
|
|
if (auto *mi = dyn_cast<MetatypeInst>(inst)) {
|
|
if (auto funcIsolation = mi->getFunction()->getActorIsolation();
|
|
funcIsolation && funcIsolation->isCallerIsolationInheriting()) {
|
|
return SILIsolationInfo::getTaskIsolated(mi)
|
|
.withNonisolatedNonsendingTaskIsolated(true);
|
|
}
|
|
|
|
return SILIsolationInfo::getTaskIsolated(mi);
|
|
}
|
|
|
|
// Check if we have an ApplyInst with nonisolated.
|
|
//
|
|
// NOTE: We purposely avoid using other isolation info from an ApplyExpr since
|
|
// when we use the isolation crossing on the ApplyExpr at this point,w e are
|
|
// unable to find out what the appropriate instance is (since we would have
|
|
// found it earlier if we could). This ensures that we can eliminate a case
|
|
// where we get a SILIsolationInfo with actor isolation and without a SILValue
|
|
// actor instance. This prevents a class of bad SILIsolationInfo merge errors
|
|
// caused by the actor instances not matching.
|
|
if (ApplyExpr *apply = inst->getLoc().getAsASTNode<ApplyExpr>()) {
|
|
if (auto crossing = apply->getIsolationCrossing()) {
|
|
if (crossing->getCalleeIsolation().isNonisolated()) {
|
|
return SILIsolationInfo::getDisconnected(false /*nonisolated(unsafe)*/);
|
|
}
|
|
}
|
|
}
|
|
|
|
return SILIsolationInfo();
|
|
}
|
|
|
|
SILIsolationInfo SILIsolationInfo::get(SILArgument *arg) {
|
|
// Return early if we do not have a non-Sendable type.
|
|
if (!SILIsolationInfo::isNonSendableType(arg->getType(), arg->getFunction()))
|
|
return {};
|
|
|
|
// Handle a switch_enum from a global-actor-isolated type.
|
|
if (auto *phiArg = dyn_cast<SILPhiArgument>(arg)) {
|
|
if (auto *singleTerm = phiArg->getSingleTerminator()) {
|
|
if (auto *swi = dyn_cast<SwitchEnumInst>(singleTerm)) {
|
|
auto enumDecl =
|
|
swi->getOperand()->getType().getEnumOrBoundGenericEnum();
|
|
return SILIsolationInfo::getGlobalActorIsolated(arg, enumDecl);
|
|
}
|
|
}
|
|
return SILIsolationInfo();
|
|
}
|
|
|
|
auto *fArg = cast<SILFunctionArgument>(arg);
|
|
|
|
// Sending is always disconnected.
|
|
if (fArg->isSending())
|
|
return SILIsolationInfo::getDisconnected(false /*nonisolated(unsafe)*/);
|
|
|
|
// Check if we have a closure captured parameter that is nonisolated(unsafe)
|
|
// at its original declaration sign. In such a case, we want to propagate that
|
|
// bit.
|
|
bool isClosureCapturedNonisolatedUnsafe = [&]() -> bool {
|
|
if (!fArg->isClosureCapture())
|
|
return false;
|
|
auto *decl = fArg->getDecl();
|
|
if (!decl)
|
|
return false;
|
|
auto *attr = decl->getAttrs().getAttribute<NonisolatedAttr>();
|
|
if (!attr)
|
|
return false;
|
|
return attr->isUnsafe();
|
|
}();
|
|
|
|
// If we have a closure capture that is not an indirect result or indirect
|
|
// result error, we want to treat it as sending so that we properly handle
|
|
// async lets.
|
|
//
|
|
// This pattern should only come up with async lets. See comment in
|
|
// canFunctionArgumentBeSent in RegionAnalysis.cpp. This code needs to stay in
|
|
// sync with that code.
|
|
if (!fArg->isIndirectResult() && !fArg->isIndirectErrorResult() &&
|
|
fArg->isClosureCapture()) {
|
|
if (auto declRef = arg->getFunction()->getDeclRef();
|
|
declRef && declRef.isAsyncLetClosure) {
|
|
return SILIsolationInfo::getDisconnected(
|
|
isClosureCapturedNonisolatedUnsafe);
|
|
}
|
|
}
|
|
|
|
// Before we do anything further, see if we have an isolated parameter. This
|
|
// handles isolated self and specifically marked isolated.
|
|
if (auto *isolatedArg = llvm::cast_or_null<SILFunctionArgument>(
|
|
fArg->getFunction()->maybeGetIsolatedArgument())) {
|
|
// See if the function is nonisolated(nonsending). In such a case, return
|
|
// task isolated.
|
|
if (auto funcIsolation = fArg->getFunction()->getActorIsolation();
|
|
funcIsolation && funcIsolation->isCallerIsolationInheriting()) {
|
|
return SILIsolationInfo::getTaskIsolated(fArg)
|
|
.withNonisolatedNonsendingTaskIsolated(true)
|
|
.withUnsafeNonIsolated(isClosureCapturedNonisolatedUnsafe);
|
|
}
|
|
|
|
auto astType = isolatedArg->getType().getASTType();
|
|
if (astType->lookThroughAllOptionalTypes()->getAnyActor()) {
|
|
if (auto isolation =
|
|
SILIsolationInfo::getActorInstanceIsolated(fArg, isolatedArg)) {
|
|
return isolation.withUnsafeNonIsolated(
|
|
isClosureCapturedNonisolatedUnsafe);
|
|
}
|
|
// If we had an isolated parameter that was an actor type, but we did not
|
|
// find any isolation for the actor instance... return {}.
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// Otherwise, see if we need to handle this isolation computation specially
|
|
// due to information from the decl ref if we have one.
|
|
if (auto declRef = fArg->getFunction()->getDeclRef()) {
|
|
// First check if we have an allocator decl ref. If we do and we have an
|
|
// actor instance isolation, then we know that we are actively just calling
|
|
// the initializer. To just make region isolation work, treat this as
|
|
// disconnected so we can construct the actor value. Users cannot write
|
|
// allocator functions so we just need to worry about compiler generated
|
|
// code. In the case of a non-actor, we can only have an allocator that is
|
|
// global-actor isolated, so we will never hit this code path.
|
|
if (declRef.kind == SILDeclRef::Kind::Allocator) {
|
|
if (auto isolation = fArg->getFunction()->getActorIsolation()) {
|
|
if (isolation->isActorInstanceIsolated()) {
|
|
return SILIsolationInfo::getDisconnected(
|
|
false /*nonisolated(unsafe)*/);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then see if we have an init accessor that is isolated to an actor
|
|
// instance, but for which we have not actually passed self. In such a case,
|
|
// we need to pass in a "fake" ActorInstance that users know is a sentinel
|
|
// for the self value.
|
|
if (auto functionIsolation = fArg->getFunction()->getActorIsolation()) {
|
|
if (functionIsolation->isActorInstanceIsolated() && declRef.getDecl()) {
|
|
if (auto *accessor =
|
|
dyn_cast_or_null<AccessorDecl>(declRef.getFuncDecl())) {
|
|
if (accessor->isInitAccessor()) {
|
|
return SILIsolationInfo::getActorInstanceIsolated(
|
|
fArg, ActorInstance::getForActorAccessorInit(),
|
|
functionIsolation->getActor());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise, if we do not have an isolated argument and are not in an
|
|
// allocator, then we might be isolated via global isolation or in a global
|
|
// actor isolated initializer.
|
|
if (auto functionIsolation = fArg->getFunction()->getActorIsolation()) {
|
|
if (functionIsolation->isActorIsolated()) {
|
|
if (functionIsolation->isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
fArg, functionIsolation->getGlobalActor())
|
|
.withUnsafeNonIsolated(isClosureCapturedNonisolatedUnsafe);
|
|
}
|
|
|
|
return SILIsolationInfo::getActorInstanceIsolated(
|
|
fArg, ActorInstance::getForActorAccessorInit(),
|
|
functionIsolation->getActor());
|
|
}
|
|
}
|
|
|
|
return SILIsolationInfo::getTaskIsolated(fArg).withUnsafeNonIsolated(
|
|
isClosureCapturedNonisolatedUnsafe);
|
|
}
|
|
|
|
/// Infer isolation region from the set of protocol conformances.
|
|
SILIsolationInfo SILIsolationInfo::getFromConformances(
|
|
SILValue value, ArrayRef<ProtocolConformanceRef> conformances) {
|
|
for (auto conformance: conformances) {
|
|
if (conformance.getProtocol()->isMarkerProtocol())
|
|
continue;
|
|
|
|
// If the conformance is a pack, recurse.
|
|
if (conformance.isPack()) {
|
|
auto pack = conformance.getPack();
|
|
for (auto innerConformance : pack->getPatternConformances()) {
|
|
auto isolation = getFromConformances(value, innerConformance);
|
|
if (isolation)
|
|
return isolation;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// If a concrete conformance is global-actor-isolated, then the resulting
|
|
// value must be.
|
|
if (conformance.isConcrete()) {
|
|
auto isolation = conformance.getConcrete()->getIsolation();
|
|
if (isolation.isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
value, isolation.getGlobalActor(), conformance.getProtocol());
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// If an abstract conformance is for a non-SendableMetatype-conforming
|
|
// type, the resulting value is task-isolated.
|
|
if (conformance.isAbstract()) {
|
|
auto sendableMetatype =
|
|
conformance.getType()->getASTContext()
|
|
.getProtocol(KnownProtocolKind::SendableMetatype);
|
|
if (sendableMetatype &&
|
|
lookupConformance(conformance.getType(), sendableMetatype,
|
|
/*allowMissing=*/false).isInvalid()) {
|
|
return SILIsolationInfo::getTaskIsolated(value,
|
|
conformance.getProtocol());
|
|
}
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
SILIsolationInfo SILIsolationInfo::getForCastConformances(
|
|
SILValue value, CanType sourceType, CanType destType) {
|
|
// If the enclosing function is @concurrent, then a cast cannot pick up
|
|
// any isolated conformances because it's not on any actor.
|
|
auto function = value->getFunction();
|
|
auto functionIsolation = function->getActorIsolation();
|
|
if (functionIsolation && functionIsolation->isNonisolated())
|
|
return {};
|
|
|
|
auto sendableMetatype =
|
|
sourceType->getASTContext().getProtocol(KnownProtocolKind::SendableMetatype);
|
|
if (!sendableMetatype)
|
|
return {};
|
|
|
|
if (!destType.isAnyExistentialType())
|
|
return {};
|
|
|
|
const auto &destLayout = destType.getExistentialLayout();
|
|
for (auto proto : destLayout.getProtocols()) {
|
|
if (proto->isMarkerProtocol())
|
|
continue;
|
|
|
|
// If the source type already conforms to the protocol, we won't be looking
|
|
// it up dynamically.
|
|
if (!lookupConformance(sourceType, proto, /*allowMissing=*/false).isInvalid())
|
|
continue;
|
|
|
|
// If the protocol inherits SendableMetatype, it can't have isolated
|
|
// conformances.
|
|
if (proto->inheritsFrom(sendableMetatype))
|
|
continue;
|
|
|
|
// The cast can produce a conformance with the same isolation as this
|
|
// function is dynamically executing. If that's known (i.e., because we're
|
|
// on a global actor), the value is isolated to that global actor.
|
|
// Otherwise, it's task-isolated.
|
|
if (functionIsolation && functionIsolation->isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
value, functionIsolation->getGlobalActor(), proto);
|
|
}
|
|
|
|
// Consider the cast to be task-isolated, because the runtime could find
|
|
// a conformance that is isolated to the current context.
|
|
return SILIsolationInfo::getTaskIsolated(value, proto);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
/// Retrieve a suitable destination value for the cast instruction.
|
|
///
|
|
/// TODO: This should probably be SILDynamicCastInst::getDest(), but that has
|
|
/// unimplemented TODOs.
|
|
static SILValue destValueForDynamicCast(SILDynamicCastInst dynCast) {
|
|
auto inst = dynCast.getInstruction();
|
|
switch (dynCast.getKind()) {
|
|
case SILDynamicCastKind::CheckedCastAddrBranchInst:
|
|
return cast<CheckedCastAddrBranchInst>(inst)->getDest();
|
|
case SILDynamicCastKind::CheckedCastBranchInst:
|
|
return cast<CheckedCastBranchInst>(inst)->getSuccessBB()->getArgument(0);
|
|
case SILDynamicCastKind::UnconditionalCheckedCastAddrInst:
|
|
return cast<UnconditionalCheckedCastAddrInst>(inst)->getDest();
|
|
case SILDynamicCastKind::UnconditionalCheckedCastInst:
|
|
return cast<UnconditionalCheckedCastInst>(inst);
|
|
}
|
|
}
|
|
|
|
SILIsolationInfo SILIsolationInfo::getConformanceIsolation(SILInstruction *inst) {
|
|
// Existential initialization.
|
|
if (auto ieai = dyn_cast<InitExistentialAddrInst>(inst)) {
|
|
return getFromConformances(ieai, ieai->getConformances());
|
|
}
|
|
if (auto ieri = dyn_cast<InitExistentialRefInst>(inst)) {
|
|
return getFromConformances(ieri, ieri->getConformances());
|
|
}
|
|
if (auto ievi = dyn_cast<InitExistentialValueInst>(inst)) {
|
|
return getFromConformances(ievi, ievi->getConformances());
|
|
}
|
|
|
|
// Dynamic casts.
|
|
if (auto dynCast = SILDynamicCastInst::getAs(inst)) {
|
|
return getForCastConformances(
|
|
destValueForDynamicCast(dynCast),
|
|
dynCast.getSourceFormalType(),
|
|
dynCast.getTargetFormalType());
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void SILIsolationInfo::printOptions(llvm::raw_ostream &os) const {
|
|
if (isolatedConformance) {
|
|
os << "isolated-conformance-to(" << isolatedConformance->getName() << ")";
|
|
}
|
|
|
|
auto opts = getOptions();
|
|
if (!opts)
|
|
return;
|
|
|
|
os << ": ";
|
|
|
|
llvm::SmallVector<StringLiteral, unsigned(Flag::MaxNumBits)> data;
|
|
|
|
if (opts.contains(Flag::UnsafeNonIsolated)) {
|
|
data.push_back(StringLiteral("nonisolated(unsafe)"));
|
|
opts -= Flag::UnsafeNonIsolated;
|
|
}
|
|
|
|
if (opts.contains(Flag::UnappliedIsolatedAnyParameter)) {
|
|
data.push_back(StringLiteral("unapplied_isolated_any_parameter"));
|
|
opts -= Flag::UnappliedIsolatedAnyParameter;
|
|
}
|
|
|
|
assert(!opts && "Unhandled flag?!");
|
|
assert(data.size() < unsigned(Flag::MaxNumBits) &&
|
|
"Please update MaxNumBits so that we can avoid heap allocations in "
|
|
"this SmallVector");
|
|
|
|
llvm::interleave(data, os, ", ");
|
|
}
|
|
|
|
StringRef SILIsolationInfo::printActorIsolationForDiagnostics(
|
|
SILFunction *fn, ActorIsolation iso, StringRef openingQuotationMark,
|
|
bool asNoun) {
|
|
SmallString<64> string;
|
|
{
|
|
llvm::raw_svector_ostream os(string);
|
|
printActorIsolationForDiagnostics(fn, iso, os, openingQuotationMark,
|
|
asNoun);
|
|
}
|
|
|
|
return fn->getASTContext().getIdentifier(string).str();
|
|
}
|
|
|
|
void SILIsolationInfo::printActorIsolationForDiagnostics(
|
|
SILFunction *fn, ActorIsolation iso, llvm::raw_ostream &os,
|
|
StringRef openingQuotationMark, bool asNoun) {
|
|
// If we have NonisolatedNonsendingByDefault enabled, we need to return
|
|
// @concurrent for nonisolated and nonisolated for caller isolation inherited.
|
|
if (fn->isAsync() && fn->getASTContext().LangOpts.hasFeature(
|
|
Feature::NonisolatedNonsendingByDefault)) {
|
|
if (iso.isCallerIsolationInheriting()) {
|
|
os << "nonisolated";
|
|
return;
|
|
}
|
|
|
|
if (iso.isNonisolated()) {
|
|
os << "@concurrent";
|
|
return;
|
|
}
|
|
}
|
|
|
|
return iso.printForDiagnostics(os, openingQuotationMark, asNoun);
|
|
}
|
|
|
|
void SILIsolationInfo::print(SILFunction *fn, llvm::raw_ostream &os) const {
|
|
switch (Kind(*this)) {
|
|
case Unknown:
|
|
os << "unknown";
|
|
return;
|
|
case Disconnected:
|
|
os << "disconnected";
|
|
printOptions(os);
|
|
return;
|
|
case Actor:
|
|
if (ActorInstance instance = getActorInstance()) {
|
|
switch (instance.getKind()) {
|
|
case ActorInstance::Kind::Value: {
|
|
SILValue value = instance.getValue();
|
|
if (auto name = VariableNameInferrer::inferName(value)) {
|
|
os << "'" << *name << "'-isolated";
|
|
printOptions(os);
|
|
os << "\n";
|
|
os << "instance: " << *value;
|
|
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ActorInstance::Kind::ActorAccessorInit:
|
|
os << "'self'-isolated";
|
|
printOptions(os);
|
|
os << '\n';
|
|
os << "instance: actor accessor init\n";
|
|
return;
|
|
case ActorInstance::Kind::CapturedActorSelf:
|
|
os << "'self'-isolated";
|
|
printOptions(os);
|
|
os << '\n';
|
|
os << "instance: captured actor instance self\n";
|
|
return;
|
|
case ActorInstance::Kind::ActorAsyncAllocatingInit:
|
|
os << "'self'-isolated";
|
|
printOptions(os);
|
|
os << '\n';
|
|
os << "instance: actor async allocating init\n";
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (getActorIsolation().getKind() == ActorIsolation::ActorInstance) {
|
|
if (auto *vd = getActorIsolation().getActorInstance()) {
|
|
os << "'" << vd->getBaseIdentifier() << "'-isolated";
|
|
printOptions(os);
|
|
return;
|
|
}
|
|
}
|
|
|
|
printActorIsolationForDiagnostics(fn, getActorIsolation(), os);
|
|
printOptions(os);
|
|
return;
|
|
case Task:
|
|
os << "task-isolated";
|
|
printOptions(os);
|
|
os << '\n';
|
|
os << "instance: " << *getIsolatedValue();
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool SILIsolationInfo::hasSameIsolation(ActorIsolation other) const {
|
|
if (getKind() != Kind::Actor)
|
|
return false;
|
|
return getActorIsolation() == other;
|
|
}
|
|
|
|
bool SILIsolationInfo::hasSameIsolation(const SILIsolationInfo &other) const {
|
|
if (getKind() != other.getKind())
|
|
return false;
|
|
|
|
switch (getKind()) {
|
|
case Unknown:
|
|
case Disconnected:
|
|
return true;
|
|
case Task:
|
|
return getIsolatedValue() == other.getIsolatedValue();
|
|
case Actor: {
|
|
ActorInstance actor1 = getActorInstance();
|
|
ActorInstance actor2 = other.getActorInstance();
|
|
|
|
// If either have an actor instance, and the actor instance doesn't match,
|
|
// return false.
|
|
//
|
|
// This ensures that cases like comparing two global-actor-isolated things
|
|
// do not hit this path.
|
|
//
|
|
// It also catches cases where we have a missing actor instance.
|
|
if ((actor1 || actor2) && actor1 != actor2)
|
|
return false;
|
|
|
|
auto lhsIsolation = getActorIsolation();
|
|
auto rhsIsolation = other.getActorIsolation();
|
|
return lhsIsolation == rhsIsolation;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SILIsolationInfo::isEqual(const SILIsolationInfo &other) const {
|
|
// First check if the two types have the same isolation.
|
|
if (!hasSameIsolation(other))
|
|
return false;
|
|
|
|
// Then check if both have the same isolated value state. If they do not
|
|
// match, bail they cannot equal.
|
|
if (hasIsolatedValue() != other.hasIsolatedValue())
|
|
return false;
|
|
|
|
// Then actually check if we have an isolated value. If we do not, then both
|
|
// do not have an isolated value due to our earlier check, so we can just
|
|
// return true early.
|
|
if (!hasIsolatedValue())
|
|
return true;
|
|
|
|
// Otherwise, equality is determined by directly comparing the isolated value.
|
|
return getIsolatedValue() == other.getIsolatedValue();
|
|
}
|
|
|
|
void SILIsolationInfo::Profile(llvm::FoldingSetNodeID &id) const {
|
|
id.AddInteger(getKind());
|
|
id.AddInteger(getOptions().toRaw());
|
|
switch (getKind()) {
|
|
case Unknown:
|
|
case Disconnected:
|
|
return;
|
|
case Task:
|
|
id.AddPointer(getIsolatedValue());
|
|
id.AddPointer(getIsolatedConformance());
|
|
return;
|
|
case Actor:
|
|
id.AddPointer(getIsolatedValue());
|
|
id.AddPointer(getIsolatedConformance());
|
|
getActorIsolation().Profile(id);
|
|
return;
|
|
}
|
|
}
|
|
|
|
StringRef SILIsolationInfo::printForDiagnostics(SILFunction *fn) const {
|
|
SmallString<64> string;
|
|
{
|
|
llvm::raw_svector_ostream os(string);
|
|
printForDiagnostics(fn, os);
|
|
}
|
|
|
|
return fn->getASTContext().getIdentifier(string).str();
|
|
}
|
|
|
|
void SILIsolationInfo::printForDiagnostics(SILFunction *fn,
|
|
llvm::raw_ostream &os) const {
|
|
switch (Kind(*this)) {
|
|
case Unknown:
|
|
llvm::report_fatal_error("Printing unknown for diagnostics?!");
|
|
return;
|
|
case Disconnected:
|
|
os << "disconnected";
|
|
return;
|
|
case Actor:
|
|
if (auto instance = getActorInstance()) {
|
|
switch (instance.getKind()) {
|
|
case ActorInstance::Kind::Value: {
|
|
SILValue value = instance.getValue();
|
|
if (auto name = VariableNameInferrer::inferName(value)) {
|
|
os << "'" << *name << "'-isolated";
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ActorInstance::Kind::ActorAccessorInit:
|
|
case ActorInstance::Kind::CapturedActorSelf:
|
|
case ActorInstance::Kind::ActorAsyncAllocatingInit:
|
|
os << "'self'-isolated";
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (getActorIsolation().getKind() == ActorIsolation::ActorInstance) {
|
|
if (auto *vd = getActorIsolation().getActorInstance()) {
|
|
os << "'" << vd->getBaseIdentifier() << "'-isolated";
|
|
return;
|
|
}
|
|
}
|
|
|
|
printActorIsolationForDiagnostics(fn, getActorIsolation(), os);
|
|
return;
|
|
case Task:
|
|
if (fn->isAsync() && fn->getASTContext().LangOpts.hasFeature(
|
|
Feature::NonisolatedNonsendingByDefault)) {
|
|
if (isNonisolatedNonsendingTaskIsolated()) {
|
|
os << "task-isolated";
|
|
return;
|
|
}
|
|
os << "@concurrent task-isolated";
|
|
return;
|
|
}
|
|
|
|
if (isNonisolatedNonsendingTaskIsolated()) {
|
|
os << "nonisolated(nonsending) task-isolated";
|
|
return;
|
|
}
|
|
|
|
os << "task-isolated";
|
|
return;
|
|
}
|
|
}
|
|
|
|
StringRef SILIsolationInfo::printForCodeDiagnostic(SILFunction *fn) const {
|
|
SmallString<64> string;
|
|
{
|
|
llvm::raw_svector_ostream os(string);
|
|
printForCodeDiagnostic(fn, os);
|
|
}
|
|
|
|
return fn->getASTContext().getIdentifier(string).str();
|
|
}
|
|
|
|
void SILIsolationInfo::printForCodeDiagnostic(SILFunction *fn,
|
|
llvm::raw_ostream &os) const {
|
|
switch (Kind(*this)) {
|
|
case Unknown:
|
|
llvm::report_fatal_error("Printing unknown for code diagnostic?!");
|
|
return;
|
|
case Disconnected:
|
|
llvm::report_fatal_error("Printing disconnected for code diagnostic?!");
|
|
return;
|
|
case Actor:
|
|
if (auto instance = getActorInstance()) {
|
|
switch (instance.getKind()) {
|
|
case ActorInstance::Kind::Value: {
|
|
SILValue value = instance.getValue();
|
|
if (auto name = VariableNameInferrer::inferName(value)) {
|
|
os << "'" << *name << "'-isolated code";
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ActorInstance::Kind::ActorAccessorInit:
|
|
case ActorInstance::Kind::CapturedActorSelf:
|
|
case ActorInstance::Kind::ActorAsyncAllocatingInit:
|
|
os << "'self'-isolated code";
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (getActorIsolation().getKind() == ActorIsolation::ActorInstance) {
|
|
if (auto *vd = getActorIsolation().getActorInstance()) {
|
|
os << "'" << vd->getBaseIdentifier() << "'-isolated code";
|
|
return;
|
|
}
|
|
}
|
|
|
|
printActorIsolationForDiagnostics(fn, getActorIsolation(), os);
|
|
os << " code";
|
|
return;
|
|
case Task:
|
|
os << "code in the current task";
|
|
return;
|
|
}
|
|
}
|
|
|
|
void SILIsolationInfo::printForOneLineLogging(SILFunction *fn,
|
|
llvm::raw_ostream &os) const {
|
|
switch (Kind(*this)) {
|
|
case Unknown:
|
|
os << "unknown";
|
|
return;
|
|
case Disconnected:
|
|
os << "disconnected";
|
|
printOptions(os);
|
|
return;
|
|
case Actor:
|
|
if (auto instance = getActorInstance()) {
|
|
switch (instance.getKind()) {
|
|
case ActorInstance::Kind::Value: {
|
|
SILValue value = instance.getValue();
|
|
if (auto name = VariableNameInferrer::inferName(value)) {
|
|
os << "'" << *name << "'-isolated";
|
|
printOptions(os);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ActorInstance::Kind::ActorAccessorInit:
|
|
os << "'self'-isolated (actor-accessor-init)";
|
|
printOptions(os);
|
|
return;
|
|
case ActorInstance::Kind::CapturedActorSelf:
|
|
os << "'self'-isolated (captured-actor-self)";
|
|
printOptions(os);
|
|
return;
|
|
case ActorInstance::Kind::ActorAsyncAllocatingInit:
|
|
os << "'self'-isolated (actor-async-allocating-init)";
|
|
printOptions(os);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (getActorIsolation().getKind() == ActorIsolation::ActorInstance) {
|
|
if (auto *vd = getActorIsolation().getActorInstance()) {
|
|
os << "'" << vd->getBaseIdentifier() << "'-isolated";
|
|
printOptions(os);
|
|
return;
|
|
}
|
|
}
|
|
|
|
printActorIsolationForDiagnostics(fn, getActorIsolation(), os);
|
|
printOptions(os);
|
|
return;
|
|
case Task:
|
|
os << "task-isolated";
|
|
printOptions(os);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check if the passed in type is NonSendable.
|
|
//
|
|
// NOTE: We special case RawPointer and NativeObject to ensure they are
|
|
// treated as non-Sendable and strict checking is applied to it.
|
|
bool SILIsolationInfo::isNonSendableType(SILType type, SILFunction *fn) {
|
|
// Treat Builtin.NativeObject, Builtin.RawPointer, and Builtin.BridgeObject as
|
|
// non-Sendable.
|
|
if (type.getASTType()->is<BuiltinNativeObjectType>() ||
|
|
type.getASTType()->is<BuiltinRawPointerType>() ||
|
|
type.getASTType()->is<BuiltinBridgeObjectType>()) {
|
|
return true;
|
|
}
|
|
|
|
// Treat Builtin.SILToken as Sendable. It cannot escape from the current
|
|
// function. We should change isSendable to hardwire this.
|
|
if (type.getASTType()->is<SILTokenType>()) {
|
|
return false;
|
|
}
|
|
|
|
// First before we do anything, see if we have a Sendable type. In such a
|
|
// case, just return true early.
|
|
//
|
|
// DISCUSSION: It is important that we do this first since otherwise calling
|
|
// getConcurrencyDiagnosticBehavior could cause us to prevent a
|
|
// "preconcurrency" unneeded diagnostic when just using Sendable values. We
|
|
// only want to trigger that if we analyze a non-Sendable type.
|
|
if (type.isSendable(fn))
|
|
return false;
|
|
|
|
// Grab out behavior. If it is none, then we have a type that we want to treat
|
|
// as non-Sendable.
|
|
auto behavior = type.getConcurrencyDiagnosticBehavior(fn);
|
|
if (!behavior)
|
|
return true;
|
|
|
|
// Finally, if we are not supposed to ignore, then we have a true non-Sendable
|
|
// type. Types whose diagnostics we are supposed to ignore, we want to treat
|
|
// as Sendable.
|
|
return *behavior != DiagnosticBehavior::Ignore;
|
|
}
|
|
|
|
SILIsolationInfo SILIsolationInfo::getFunctionIsolation(SILFunction *fn) {
|
|
auto isolation = fn->getActorIsolation();
|
|
if (!isolation)
|
|
return {};
|
|
|
|
if (isolation->isGlobalActor()) {
|
|
return SILIsolationInfo::getGlobalActorIsolated(
|
|
SILValue(), isolation->getGlobalActor());
|
|
}
|
|
|
|
if (isolation->isActorInstanceIsolated()) {
|
|
return SILIsolationInfo::getActorInstanceIsolated(
|
|
SILValue(), cast<SILFunctionArgument>(fn->maybeGetIsolatedArgument()));
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: ActorInstance
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
SILValue ActorInstance::lookThroughInsts(SILValue value) {
|
|
if (!value)
|
|
return value;
|
|
|
|
while (auto *svi = dyn_cast<SingleValueInstruction>(value)) {
|
|
if (isa<EndInitLetRefInst>(svi) || isa<CopyValueInst>(svi) ||
|
|
isa<MoveValueInst>(svi) || isa<ExplicitCopyValueInst>(svi) ||
|
|
isa<BeginBorrowInst>(svi) ||
|
|
isa<CopyableToMoveOnlyWrapperValueInst>(svi) ||
|
|
isa<MoveOnlyWrapperToCopyableValueInst>(svi) ||
|
|
isa<InitExistentialRefInst>(svi) || isa<UncheckedRefCastInst>(svi) ||
|
|
isa<UnconditionalCheckedCastInst>(svi)) {
|
|
value = lookThroughInsts(svi->getOperand(0));
|
|
continue;
|
|
}
|
|
|
|
// Look through extracting from optionals.
|
|
if (auto *uedi = dyn_cast<UncheckedEnumDataInst>(svi)) {
|
|
if (uedi->getEnumDecl() ==
|
|
uedi->getFunction()->getASTContext().getOptionalDecl()) {
|
|
value = lookThroughInsts(uedi->getOperand());
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Look through wrapping in an optional.
|
|
if (auto *ei = dyn_cast<EnumInst>(svi)) {
|
|
if (ei->hasOperand()) {
|
|
if (ei->getElement()->getParentEnum() ==
|
|
ei->getFunction()->getASTContext().getOptionalDecl()) {
|
|
value = lookThroughInsts(ei->getOperand());
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// See if this is distributed asLocalActor. In such a case, we want to
|
|
// consider the result actor to be the same actor as the input isolated
|
|
// parameter.
|
|
if (auto fas = FullApplySite::isa(svi)) {
|
|
if (auto *functionRef = fas.getReferencedFunctionOrNull()) {
|
|
if (auto declRef = functionRef->getDeclRef()) {
|
|
if (auto *accessor =
|
|
dyn_cast_or_null<AccessorDecl>(declRef.getFuncDecl())) {
|
|
if (auto asLocalActorDecl =
|
|
getDistributedActorAsLocalActorComputedProperty(
|
|
functionRef->getDeclContext()->getParentModule())) {
|
|
if (auto asLocalActorGetter =
|
|
asLocalActorDecl->getAccessor(AccessorKind::Get);
|
|
asLocalActorGetter && asLocalActorGetter == accessor) {
|
|
value = lookThroughInsts(
|
|
fas.getIsolatedArgumentOperandOrNullPtr()->get());
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: SILDynamicMergedIsolationInfo
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
std::optional<SILDynamicMergedIsolationInfo>
|
|
SILDynamicMergedIsolationInfo::merge(SILIsolationInfo other) const {
|
|
// If we are greater than the other kind, then we are further along the
|
|
// lattice. We ignore the change.
|
|
//
|
|
// NOTE: If we are further along, then we both cannot be task isolated. In
|
|
// such a case, we are the only potential thing that can be
|
|
// nonisolated(unsafe)... so we do not need to try to propagate.
|
|
if (unsigned(innerInfo.getKind() > unsigned(other.getKind()))) {
|
|
return {*this};
|
|
}
|
|
|
|
// If we are both actor isolated...
|
|
if (innerInfo.isActorIsolated() && other.isActorIsolated()) {
|
|
// If both innerInfo and other have the same isolation, we are obviously
|
|
// done. Just return innerInfo since we could return either.
|
|
if (innerInfo.hasSameIsolation(other))
|
|
return {innerInfo.withMergedIsolatedConformance(other.getIsolatedConformance())};
|
|
|
|
// Ok, there is some difference in between innerInfo and other. Lets see if
|
|
// they are both actor instance isolated and if either are unapplied
|
|
// isolated any parameter. In such a case, take the one that is further
|
|
// along.
|
|
if (innerInfo.getActorIsolation().isActorInstanceIsolated() &&
|
|
other.getActorIsolation().isActorInstanceIsolated()) {
|
|
if (innerInfo.isUnappliedIsolatedAnyParameter())
|
|
return other.withMergedIsolatedConformance(innerInfo.getIsolatedConformance());
|
|
if (other.isUnappliedIsolatedAnyParameter())
|
|
return innerInfo.withMergedIsolatedConformance(other.getIsolatedConformance());
|
|
}
|
|
|
|
// Otherwise, they do not match... so return None to signal merge failure.
|
|
return {};
|
|
}
|
|
|
|
// If we are both disconnected and other has the unsafeNonIsolated bit set,
|
|
// drop that bit and return that.
|
|
//
|
|
// DISCUSSION: We do not want to preserve the unsafe non isolated bit after
|
|
// merging. These bits should not propagate through merging and should instead
|
|
// always be associated with non-merged infos.
|
|
if (other.isDisconnected() && other.isUnsafeNonIsolated()) {
|
|
return {other.withUnsafeNonIsolated(false)};
|
|
}
|
|
|
|
// We know that we are either the same as other or other is further along. If
|
|
// other is further along, it is the only thing that can propagate the task
|
|
// isolated bit. So we do not need to do anything. If we are equal though, we
|
|
// may need to propagate the bit. This ensures that when we emit a diagnostic
|
|
// we appropriately say potentially actor isolated code instead of code in the
|
|
// current task.
|
|
//
|
|
// TODO: We should really represent this as a separate isolation info
|
|
// kind... but that would be a larger change than we want for 6.2.
|
|
if (innerInfo.isTaskIsolated() && other.isTaskIsolated()) {
|
|
if (innerInfo.isNonisolatedNonsendingTaskIsolated() ||
|
|
other.isNonisolatedNonsendingTaskIsolated())
|
|
return other.withNonisolatedNonsendingTaskIsolated(true)
|
|
.withMergedIsolatedConformance(innerInfo.getIsolatedConformance());
|
|
}
|
|
|
|
// Otherwise, just return other.
|
|
return {other};
|
|
}
|
|
|
|
void ActorInstance::print(llvm::raw_ostream &os) const {
|
|
os << "Actor Instance. Kind: ";
|
|
switch (getKind()) {
|
|
case Kind::Value:
|
|
os << "Value.";
|
|
break;
|
|
case Kind::ActorAccessorInit:
|
|
os << "ActorAccessorInit.";
|
|
break;
|
|
case Kind::CapturedActorSelf:
|
|
os << "CapturedActorSelf.";
|
|
break;
|
|
case Kind::ActorAsyncAllocatingInit:
|
|
os << "ActorAsyncAllocatingInit.";
|
|
break;
|
|
}
|
|
|
|
if (auto value = maybeGetValue()) {
|
|
os << " Value: " << value;
|
|
};
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Tests
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace swift::test {
|
|
|
|
// Arguments:
|
|
// - SILValue: value to look up isolation for.
|
|
// Dumps:
|
|
// - The inferred isolation.
|
|
static FunctionTest
|
|
IsolationInfoInferrence("sil_isolation_info_inference",
|
|
[](auto &function, auto &arguments, auto &test) {
|
|
auto value = arguments.takeValue();
|
|
|
|
SILIsolationInfo info =
|
|
SILIsolationInfo::get(value);
|
|
llvm::outs() << "Input Value: " << *value;
|
|
llvm::outs() << "Isolation: ";
|
|
info.printForOneLineLogging(&function,
|
|
llvm::outs());
|
|
llvm::outs() << "\n";
|
|
});
|
|
|
|
// Arguments:
|
|
// - SILValue: first value to merge
|
|
// - SILValue: second value to merge
|
|
// Dumps:
|
|
// - The merged isolation.
|
|
static FunctionTest IsolationMergeTest(
|
|
"sil-isolation-info-merged-inference",
|
|
[](auto &function, auto &arguments, auto &test) {
|
|
auto firstValue = arguments.takeValue();
|
|
auto secondValue = arguments.takeValue();
|
|
SILIsolationInfo firstValueInfo = SILIsolationInfo::get(firstValue);
|
|
SILIsolationInfo secondValueInfo = SILIsolationInfo::get(secondValue);
|
|
std::optional<SILDynamicMergedIsolationInfo> mergedInfo(firstValueInfo);
|
|
mergedInfo = mergedInfo->merge(secondValueInfo);
|
|
llvm::outs() << "First Value: " << *firstValue;
|
|
llvm::outs() << "First Isolation: ";
|
|
firstValueInfo.printForOneLineLogging(&function, llvm::outs());
|
|
llvm::outs() << "\nSecond Value: " << *secondValue;
|
|
llvm::outs() << "Second Isolation: ";
|
|
secondValueInfo.printForOneLineLogging(&function, llvm::outs());
|
|
llvm::outs() << "\nMerged Isolation: ";
|
|
if (mergedInfo) {
|
|
mergedInfo->printForOneLineLogging(&function, llvm::outs());
|
|
} else {
|
|
llvm::outs() << "Merge failure!";
|
|
}
|
|
llvm::outs() << "\n";
|
|
});
|
|
|
|
} // namespace swift::test
|