Files
swift-mirror/lib/SILOptimizer/Utils/SILIsolationInfo.cpp
Michael Gottesman 399bb5d9a1 [rbi] Replace "task-isolated" with "code in the current isolation context" in diagnostics
"Task-isolated" is an internal implementation term that is not meaningful
to users. Replace it with the user-facing "code in the current isolation context"
formulation throughout region-based isolation diagnostics.

Diagnostic definitions in DiagnosticsSIL.def are updated to accept an
additional bool parameter for task-isolation and use %select to choose
between the actor-isolation string and "code in the current isolation context".
Emission sites in SendNonSendable.cpp are updated to pass
isTaskIsolated() at each call site. All affected test expectations are
updated to match the new output.

rdar://178534037
2026-06-02 09:29:58 -07:00

2093 lines
77 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);
}
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::isSendable(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;
}
static const SILFunctionArgument *
getSelfFunctionArgumentForRefElementAddr(SILValue value) {
auto *reai = dyn_cast<RefElementAddrInst>(value);
if (!reai)
return {};
auto self = llvm::cast_or_null<SILFunctionArgument>(
reai->getFunction()->maybeGetSelfArgument());
if (!self || !self->getType().isAnyActor())
return {};
auto declRef = reai->getFunction()->getDeclRef();
if (!declRef || declRef.kind != SILDeclRef::Kind::Initializer)
return {};
auto *lbi = dyn_cast<LoadBorrowInst>(reai->getOperand());
if (!lbi)
return {};
auto *asi = dyn_cast<AllocStackInst>(lbi->getOperand());
if (!asi || !asi->getDecl()->isActorSelf())
return {};
return self;
}
/// 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 a global actor isolated field, then prefer that.
//
// NOTE: This handles both cases where the field has an explicit isolation and
// a field where the field does not have an explicit isolation but inherits
// its isolation from its parent type since swift::getActorIsolation handles
// this for us.
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 or
// nonisolated(unsafe). If so, return early here.
if (varIsolation.isNonisolatedOrConcurrent())
return SILIsolationInfo::getDisconnected(
varIsolation.isNonisolatedUnsafe());
// See if we have a load from a box in an objc actor initializer where we need
// to identify our actor instance (which would be a box otherwise) with self.
if (auto newValue = getSelfFunctionArgumentForRefElementAddr(queriedValue)) {
if (auto info =
SILIsolationInfo::getActorInstanceIsolated(queriedValue, newValue))
return info;
}
// Check if we actually have an actor as our class value. 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;
}
}
}
// Then check if our classValue is an any Actor.
if (auto *nomDecl = classValue->getType().getNominalOrBoundGenericNominal();
nomDecl && nomDecl->isAnyActor()) {
return SILIsolationInfo::getActorInstanceIsolated(queriedValue, classValue,
nomDecl);
}
// 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());
// 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 check if we are passing the implicit builtin actor to this. In
// such a case, this is nonisolated(nonsending).
if (auto *fArg = dyn_cast<SILFunctionArgument>(actualIsolatedValue);
fArg && fArg->isImplicitBuiltinActor()) {
// TODO: We probably should split this API so that the user in the case
// where we are asking for a specific value, we use that value and if we
// are asking for the instruction generally (for instance if we are
// generating an actor introducing value), we set a bit on the
// SILIsolationInfo saying that the isolation was introduced by the
// instruction. This is important for isolation history eventually.
return SILIsolationInfo::getTaskIsolated(SILValue())
.withNonisolatedNonsendingTaskIsolated(true);
}
// 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)) {
auto isolation = fri->getReferencedFunction()->getActorIsolation();
// 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());
}
return SILIsolationInfo::getDisconnected(
varIsolation.isNonisolatedUnsafe());
}
if (auto *seai = dyn_cast<StructElementAddrInst>(inst)) {
auto varIsolation = swift::getActorIsolation(seai->getField());
// If our var is explicitly global actor isolated, return it.
if (varIsolation.isGlobalActor()) {
return SILIsolationInfo::getGlobalActorIsolated(
seai, varIsolation.getGlobalActor());
}
// Otherwise, just return disconnected.
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());
}
if (auto *utedi = dyn_cast<UncheckedEnumDataAddrInstBase>(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::isNonSendable(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)*/);
}
}
}
}
}
}
if (auto *bbi = dyn_cast<BeginBorrowInst>(inst)) {
if (bbi->isFromVarDecl()) {
// See if we have the actual AST information on our instruction.
if (auto *varDecl = bbi->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 isolated to the context in which it
/// is initialized so they cannot into another isolation domain.
if (auto *mi = dyn_cast<MetatypeInst>(inst)) {
auto funcIsolation = mi->getFunction()->getActorIsolation();
if (funcIsolation.isNonisolatedNonsending()) {
return SILIsolationInfo::getTaskIsolated(mi)
.withNonisolatedNonsendingTaskIsolated(true);
}
if (funcIsolation.isGlobalActor()) {
return SILIsolationInfo::getGlobalActorIsolated(
mi, funcIsolation.getGlobalActor());
}
if (funcIsolation.isActorInstanceIsolated()) {
if (auto *iso = llvm::cast_or_null<SILFunctionArgument>(
mi->getFunction()->maybeGetIsolatedArgument())) {
return SILIsolationInfo::getActorInstanceIsolated(mi, iso);
}
}
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().isNonisolatedOrConcurrent()) {
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::isNonSendable(arg))
return {};
if (auto *phiArg = dyn_cast<SILPhiArgument>(arg)) {
if (auto *singleTerm = phiArg->getSingleTerminator()) {
// Handle a switch_enum from a global-actor-isolated type.
if (auto *swi = dyn_cast<SwitchEnumInst>(singleTerm)) {
auto enumDecl =
swi->getOperand()->getType().getEnumOrBoundGenericEnum();
return SILIsolationInfo::getGlobalActorIsolated(arg, enumDecl);
}
// Handle a checked_cast_br argument that involves an isolated
// conformance. The conformance only changes for the first element.
if (auto *ccbi = dyn_cast<CheckedCastBranchInst>(singleTerm);
ccbi && ccbi->getSuccessBB() == phiArg->getParent()) {
if (auto isolation = SILIsolationInfo::getConformanceIsolation(ccbi)) {
return isolation;
}
}
}
// Otherwise assume that we are disconnected. We will rely on merging.
return SILIsolationInfo::getDisconnected(false /*nonisolated(unsafe)*/);
}
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();
}();
auto *func = fArg->getFunction();
// 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 = func->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>(
func->maybeGetIsolatedArgument())) {
// See if the function is nonisolated(nonsending). In such a case, return
// task isolated.
if (func->getActorIsolation().isNonisolatedNonsending()) {
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 = func->getDeclRef()) {
auto funcIsolation = func->getActorIsolation();
// 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 &&
funcIsolation.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 (funcIsolation.isActorInstanceIsolated() && declRef.getDecl()) {
if (auto *accessor =
dyn_cast_or_null<AccessorDecl>(declRef.getFuncDecl())) {
if (accessor->isInitAccessor()) {
return SILIsolationInfo::getActorInstanceIsolated(
fArg, ActorInstance::getForActorAccessorInit(),
funcIsolation.getActor());
}
}
}
// Check if we have a nonisolated synchronous initializer for an actor. In
// such a case, we want to treat the non-Sendable parameters as being in
// the actor's isolation domain. This is a special case from the region
// isolation proposal and ensures that nonisolated async and sync
// initializers act the same way.
if (funcIsolation.isNonisolated() &&
declRef.kind == SILDeclRef::Kind::Initializer &&
!func->getLoweredFunctionType()->isAsync()) {
// Just performing some defensive checks here.
if (auto *self =
cast_or_null<SILFunctionArgument>(func->getSelfArgument());
self && self->getType().isAnyActor()) {
return SILIsolationInfo::getActorInstanceIsolated(fArg, self);
}
}
}
// 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.
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()) {
auto functionIsolation = value->getFunction()->getActorIsolation();
if (functionIsolation.isGlobalActor()) {
return SILIsolationInfo::getGlobalActorIsolated(
value, functionIsolation.getGlobalActor(),
conformance.getProtocol());
}
if (functionIsolation.isActorInstanceIsolated()) {
if (auto isolatedParam = cast_or_null<SILFunctionArgument>(
value->getFunction()->maybeGetIsolatedArgument())) {
if (auto result = SILIsolationInfo::getActorInstanceIsolated(
value, isolatedParam, conformance.getProtocol())) {
return result;
}
}
}
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.isNonisolatedOrConcurrent())
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 or in a an actor instance method), the value could be
// isolated to that actor. Otherwise, if we are nonisolated, it's
// task-isolated.
//
// DISCUSSION: We assume that if it were not possible to get a conformance
// that is isolated to the current context, then we would emit an error in
// the type checker. The fact that we were let through means that the
// runtime will return nil or produce a value that is isolated to the
// current isolation domain.
if (functionIsolation.isGlobalActor()) {
return SILIsolationInfo::getGlobalActorIsolated(
value, functionIsolation.getGlobalActor(), proto);
}
if (functionIsolation.isActorInstanceIsolated()) {
if (auto isolatedParam = cast_or_null<SILFunctionArgument>(
value->getFunction()->maybeGetIsolatedArgument())) {
if (auto result = SILIsolationInfo::getActorInstanceIsolated(
value, isolatedParam, proto)) {
return result;
}
}
}
// 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;
}
if (opts.contains(Flag::NonisolatedNonsendingTaskIsolated)) {
data.push_back(
StringLiteral("task_isolated_from_nonisolated_nonsending_parameter"));
opts -= Flag::NonisolatedNonsendingTaskIsolated;
}
assert(!opts && "Unhandled flag?!");
assert(data.size() < unsigned(Flag::MaxNumBits) &&
"Please update MaxNumBits so that we can avoid heap allocations in "
"this SmallVector");
llvm::interleaveComma(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) {
// Under NonisolatedNonsendingByDefault, render NonisolatedNonsending as
// "nonisolated" instead of the default "caller isolation inheriting-isolated".
if (fn->isAsync() &&
fn->getASTContext().LangOpts.hasFeature(
Feature::NonisolatedNonsendingByDefault) &&
iso.isNonisolatedNonsending()) {
os << "nonisolated";
return;
}
return iso.printForDiagnostics(os, openingQuotationMark, asNoun);
}
void SILIsolationInfo::print(SILFunction *fn, llvm::raw_ostream &os) const {
switch (Kind(*this)) {
case Invalid:
os << "invalid";
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 Invalid:
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 Invalid:
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 Invalid:
llvm::report_fatal_error("Printing invalid 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 Invalid:
llvm::report_fatal_error("Printing invalid 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 isolation context";
return;
}
}
void SILIsolationInfo::printForOneLineLogging(SILFunction *fn,
llvm::raw_ostream &os) const {
switch (Kind(*this)) {
case Invalid:
os << "invalid";
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;
}
}
bool SILIsolationInfo::isSendable(SILValue value) {
// If the type system says we are sendable, then we are always sendable.
if (isSendableType(value->getType(), value->getFunction()))
return true;
if (auto *fArg = dyn_cast<SILFunctionArgument>(value);
fArg && fArg->isClosureCapture() && fArg->isInferredImmutable()) {
CanSILBoxType boxType = fArg->getType().getAs<SILBoxType>();
if (!boxType || boxType->getNumFields() != 1)
return false;
auto innerType = boxType->getFieldType(*fArg->getFunction(), 0);
// We can only do this if the underlying type is Sendable.
if (isNonSendableType(innerType, fArg->getFunction()))
return false;
// For now to be conservative, only do this if we have a weak parameter.
if (auto ownership = innerType.getReferenceStorageOwnership();
!ownership || *ownership != ReferenceOwnership::Weak)
return false;
// Ok, we can treat this as Sendable.
return true;
}
if (auto *abi = dyn_cast<AllocBoxInst>(lookThroughOwnershipInsts(value));
abi && abi->isInferredImmutable()) {
CanSILBoxType boxType = abi->getType().castTo<SILBoxType>();
if (boxType->getNumFields() != 1)
return false;
auto innerType = boxType->getFieldType(*abi->getFunction(), 0);
if (isNonSendableType(innerType, abi->getFunction()))
return false;
// For now to be conservative, only do this if we have a weak parameter.
if (auto ownership = innerType.getReferenceStorageOwnership();
!ownership || *ownership != ReferenceOwnership::Weak)
return false;
return true;
}
return false;
}
// 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;
// See if we have an immutable box that contains only Sendable things. In such
// a case, we treat the box itself as Sendable.
//
// One example of such a type is a Sendable noncopyable let that is captured
// by an escaping closure.
if (auto *boxTy = type.getASTType()->getAs<SILBoxType>();
boxTy &&
llvm::all_of(range(boxTy->getNumFields()), [&](unsigned index) -> bool {
return !boxTy->isFieldMutable(index) &&
isSendableType(boxTy->getFieldType(*fn, index), 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) ||
isa<ImplicitActorToOpaqueIsolationCastInst>(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
//===----------------------------------------------------------------------===//
SILDynamicMergedIsolationInfo
SILDynamicMergedIsolationInfo::merge(SILIsolationInfo other) const {
auto lhs = innerInfo;
auto rhs = other;
if (lhs.getKind() > rhs.getKind())
std::swap(lhs, rhs);
#ifdef KIND_COMBINE
#error "KIND_COMBINE already defined?!"
#endif
#ifdef KIND_COMBINE_DECL
#error "KIND_COMBINE_DECL already defined?!"
#endif
#define KIND_COMBINE(X, Y) (uint8_t(X) | (uint8_t(Y) << 2))
enum class CombinedKind : uint8_t {
#define KIND_COMBINE_DECL(X, Y) \
X##Y = KIND_COMBINE(SILIsolationInfo::Kind::X, SILIsolationInfo::Kind::Y)
KIND_COMBINE_DECL(Disconnected, Disconnected),
KIND_COMBINE_DECL(Disconnected, Task),
KIND_COMBINE_DECL(Disconnected, Actor),
KIND_COMBINE_DECL(Disconnected, Invalid),
KIND_COMBINE_DECL(Task, Task),
KIND_COMBINE_DECL(Task, Actor),
KIND_COMBINE_DECL(Task, Invalid),
KIND_COMBINE_DECL(Actor, Actor),
KIND_COMBINE_DECL(Actor, Invalid),
KIND_COMBINE_DECL(Invalid, Invalid)
};
switch (CombinedKind(KIND_COMBINE(lhs.getKind(), rhs.getKind()))) {
case CombinedKind::DisconnectedDisconnected:
// If we are both disconnected and rhs 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 (rhs.isDisconnected() && rhs.isUnsafeNonIsolated()) {
return {rhs.withUnsafeNonIsolated(false)};
}
return {lhs};
case CombinedKind::DisconnectedTask:
case CombinedKind::DisconnectedActor:
case CombinedKind::DisconnectedInvalid:
return {rhs};
case CombinedKind::TaskTask:
if (lhs.isNonisolatedNonsendingTaskIsolated() ||
rhs.isNonisolatedNonsendingTaskIsolated())
return lhs.withNonisolatedNonsendingTaskIsolated(true)
.withMergedIsolatedConformance(rhs.getIsolatedConformance());
return {rhs};
case CombinedKind::TaskActor:
// If we have a nonisolated(nonsending) task isolated value and an unapplied
// isolated any parameter, allow for it to merge by just taking the isolated
// any parameter.
//
// TODO: Should we model unapplied isolated any parameter as a separate
// lattice element?
//
// TODO: Should we model NonisolatedNonsendingTaskIsolated as a separate
// kind. It is sort of an Actor, but we do not want to treat it like an
// actor when emitting diagnostics and the like.
if (lhs.isNonisolatedNonsendingTaskIsolated() &&
rhs.isUnappliedIsolatedAnyParameter()) {
return {lhs};
}
// Otherwise, fail.
return {SILIsolationInfo()};
case CombinedKind::TaskInvalid:
return {rhs};
case CombinedKind::ActorActor: {
// If both lhs and rhs have the same isolation, we are obviously
// done. Just return lhs since we could return either.
if (lhs.hasSameIsolation(rhs))
return {lhs.withMergedIsolatedConformance(rhs.getIsolatedConformance())};
// Ok, there is some difference in between lhs and rhs. 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 (lhs.getActorIsolation().isActorInstanceIsolated() &&
rhs.getActorIsolation().isActorInstanceIsolated()) {
if (lhs.isUnappliedIsolatedAnyParameter())
return rhs.withMergedIsolatedConformance(lhs.getIsolatedConformance());
if (rhs.isUnappliedIsolatedAnyParameter())
return lhs.withMergedIsolatedConformance(rhs.getIsolatedConformance());
}
return {SILIsolationInfo()};
}
case CombinedKind::ActorInvalid:
case CombinedKind::InvalidInvalid:
return {rhs};
}
llvm_unreachable("Unhandled case?!");
#undef KIND_COMBINE_DECL
#undef KIND_COMBINE
}
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";
});
// Test: sil_isolation_info_is_sendable
//
// Checks whether a SILValue is considered Sendable by the region-based
// isolation analysis. This is different from sil_isolation_info_inference which
// returns the *isolation* of a value (task-isolated, actor-isolated, etc.).
//
// Use this test to verify Sendable inference for:
// - Box types (alloc_box), especially immutable boxes with Sendable contents
// - Values where Sendability affects region analysis but not isolation
//
// Arguments:
// - SILValue: value to run SILIsolationInfo::isSendable upon
//
// Output format:
// Input Value: <SIL instruction>
// IsSendable: yes|no
//
// Example usage in .sil test:
// specify_test "sil_isolation_info_is_sendable @trace[0]"
// %0 = alloc_box ${ let MyActor }
// debug_value [trace] %0
// // CHECK: IsSendable: yes
static FunctionTest
IsSendableInference("sil_isolation_info_is_sendable",
[](auto &function, auto &arguments, auto &test) {
auto value = arguments.takeValue();
bool isSendable = SILIsolationInfo::isSendable(value);
llvm::outs() << "Input Value: " << *value;
llvm::outs() << "IsSendable: "
<< (isSendable ? "yes\n" : "no\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);
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";
});
// Arguments:
// - SILValue: value to look up isolation for.
// Dumps:
// - The inferred isolation.
static FunctionTest
GetConformanceIsolationInferrence("sil_isolation_info_get_conformance_isolation_inferrence",
[](auto &function, auto &arguments, auto &test) {
auto value = arguments.takeValue();
SILIsolationInfo info =
SILIsolationInfo::getConformanceIsolation(cast<SingleValueInstruction>(value));
llvm::outs() << "Input Value: " << *value;
llvm::outs() << "Isolation: ";
info.printForOneLineLogging(&function,
llvm::outs());
llvm::outs() << "\n";
});
} // namespace swift::test