mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
We want 'inout sending' parameters to have the semantics that not only are they
disconnected on return from the function but additionally they are guaranteed to
be in their own disconnected region on return. This implies that we must emit
errors when an 'inout sending' parameter or any element that is in the same
region as the current value within an 'inout sending' parameter is
returned. This commit contains a new diagnostic for RegionIsolation that adds
specific logic for detecting and emitting errors in these situations.
To implement this, we introduce 3 new diagnostics with each individual
diagnostic being slightly different to reflect the various ways that this error
can come up in source:
* Returning 'inout sending' directly:
```swift
func returnInOutSendingDirectly(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
return x // expected-warning {{cannot return 'inout sending' parameter 'x' from global function 'returnInOutSendingDirectly'}}
// expected-note @-1 {{returning 'x' risks concurrent access since caller assumes that 'x' and the result of global function 'returnInOutSendingDirectly' can be safely sent to different isolation domains}}
}
```
* Returning a value in the same region as an 'inout sending' parameter. E.x.:
```swift
func returnInOutSendingRegionVar(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
var y = x
y = x
return y // expected-warning {{cannot return 'y' from global function 'returnInOutSendingRegionVar'}}
// expected-note @-1 {{returning 'y' risks concurrent access to 'inout sending' parameter 'x' since the caller assumes that 'x' and the result of global function 'returnInOutSendingRegionVar' can be safely sent to different isolation domains}}
}
```
* Returning the result of a function or computed property that is in the same
region as the 'inout parameter'.
```swift
func returnInOutSendingViaHelper(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
let y = x
return useNonSendableKlassAndReturn(y) // expected-warning {{cannot return result of global function 'useNonSendableKlassAndReturn' from global function 'returnInOutSendingViaHelper'}}
// expected-note @-1 {{returning result of global function 'useNonSendableKlassAndReturn' risks concurrent access to 'inout sending' parameter 'x' since the caller assumes that 'x' and the result of global function 'returnInOutSendingViaHelper' can be safely sent to different isolation domains}}
}
```
Additionally, I had to introduce a specific variant for each of these
diagnostics for cases where due to us being in a method, we are actually in our
caller causing the 'inout sending' parameter to be in the same region as an
actor isolated value:
* Returning 'inout sending' directly:
```swift
extension MyActor {
func returnInOutSendingDirectly(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
return x // expected-warning {{cannot return 'inout sending' parameter 'x' from instance method 'returnInOutSendingDirectly'}}
// expected-note @-1 {{returning 'x' risks concurrent access since caller assumes that 'x' is not actor-isolated and the result of instance method 'returnInOutSendingDirectly' is 'self'-isolated}}
}
}
```
* Returning a value in the same region as an 'inout sending' parameter. E.x.:
```swift
extension MyActor {
func returnInOutSendingRegionLet(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
let y = x
return y // expected-warning {{cannot return 'y' from instance method 'returnInOutSendingRegionLet'}}
// expected-note @-1 {{returning 'y' risks concurrent access to 'inout sending' parameter 'x' since the caller assumes that 'x' is not actor-isolated and the result of instance method 'returnInOutSendingRegionLet' is 'self'-isolated}}
}
}
```
* Returning the result of a function or computed property that is in the same region as the 'inout parameter'.
```swift
extension MyActor {
func returnInOutSendingViaHelper(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
let y = x
return useNonSendableKlassAndReturn(y) // expected-warning {{cannot return result of global function 'useNonSendableKlassAndReturn' from instance method 'returnInOutSendingViaHelper'; this is an error in the Swift 6 language mode}}
// expected-note @-1 {{returning result of global function 'useNonSendableKlassAndReturn' risks concurrent access to 'inout sending' parameter 'x' since the caller assumes that 'x' is not actor-isolated and the result of instance method 'returnInOutSendingViaHelper' is 'self'-isolated}}
}
}
```
To implement this, I used two different approaches depending on whether or not
the returned value was generic or not.
* Concrete
In the case where we had a concrete value, I was able to in simple cases emit
diagnostics based off of the values returned by the return inst. In cases where
we phied together results due to multiple results in the same function, we
determine which of the incoming phied values caused the error by grabbing the
exit partition information of each of the incoming value predecessors and seeing
if an InOutSendingAtFunctionExit would emit an error.
* Generic
In the case of generic code, it is a little more interesting since the result is
a value stored in an our parameter instead of being a value directly returned by
a return inst. To work around this, I use PrunedLiveness to determine the last
values stored into the out parameter in the function to avoid having to do a
full dataflow. Then I take the exit blocks where we assign each of those values
and run the same check as we do in the direct phi case to emit the appropriate
error.
rdar://152454571
694 lines
24 KiB
C++
694 lines
24 KiB
C++
//===--- SILIsolationInfo.h -----------------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SWIFT_SILOPTIMIZER_UTILS_SILISOLATIONINFO_H
|
|
#define SWIFT_SILOPTIMIZER_UTILS_SILISOLATIONINFO_H
|
|
|
|
#include "swift/Basic/Defer.h"
|
|
#include "swift/Basic/FrozenMultiMap.h"
|
|
#include "swift/Basic/ImmutablePointerSet.h"
|
|
#include "swift/Basic/LLVM.h"
|
|
#include "swift/SIL/SILFunction.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
|
|
#include "llvm/ADT/MapVector.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
#include <algorithm>
|
|
#include <variant>
|
|
|
|
namespace swift {
|
|
|
|
/// Represents a generalized actor instance reference.
|
|
///
|
|
/// Used to generalize over cases where we actually have an actor instance
|
|
/// associated with a SILValue and other cases where we know that the actor
|
|
/// instance is but we don't have a value.
|
|
class ActorInstance {
|
|
public:
|
|
enum class Kind : uint8_t {
|
|
/// An actor instance where we have an actual SILValue for the actor
|
|
/// instance.
|
|
Value,
|
|
|
|
/// An actor instance in an actor accessor init where we do not have direct
|
|
/// access to the self value and instead have access indirectly to the
|
|
/// storage associated with the accessor.
|
|
ActorAccessorInit = 0x1,
|
|
|
|
/// An actor instance that is represented by "self" being captured in a
|
|
/// closure of some sort. In such a case, we do not know which of the
|
|
/// parameters are the true "self" (since the closure is a thin
|
|
/// function)... so we just use an artificial ActorInstance to represent
|
|
/// self in this case.
|
|
CapturedActorSelf = 0x2,
|
|
};
|
|
|
|
/// Set to (SILValue(), $KIND) if we have an ActorAccessorInit|CapturedSelf.
|
|
/// Is null if we have (SILValue(), Kind::Value).
|
|
llvm::PointerIntPair<SILValue, 2> value;
|
|
|
|
ActorInstance(SILValue value, Kind kind)
|
|
: value(value, std::underlying_type<Kind>::type(kind)) {}
|
|
|
|
/// We want to look through certain instructions like end_init_ref that have
|
|
/// the appropriate actor type but could disguise the actual underlying value
|
|
/// that we want to represent our actor.
|
|
static SILValue lookThroughInsts(SILValue value);
|
|
|
|
public:
|
|
ActorInstance() : ActorInstance(SILValue(), Kind::Value) {}
|
|
|
|
static ActorInstance getForValue(SILValue value) {
|
|
if (!value)
|
|
return ActorInstance();
|
|
value = lookThroughInsts(value);
|
|
if (!value->getType()
|
|
.getASTType()
|
|
->lookThroughAllOptionalTypes()
|
|
->isAnyActorType())
|
|
return ActorInstance();
|
|
return ActorInstance(value, Kind::Value);
|
|
}
|
|
|
|
/// See Kind::ActorAccessorInit for explanation on what a ActorAccessorInit
|
|
/// is.
|
|
static ActorInstance getForActorAccessorInit() {
|
|
return ActorInstance(SILValue(), Kind::ActorAccessorInit);
|
|
}
|
|
|
|
/// See Kind::CapturedActorSelf for explanation on what a CapturedActorSelf
|
|
/// is.
|
|
static ActorInstance getForCapturedSelf() {
|
|
return ActorInstance(SILValue(), Kind::CapturedActorSelf);
|
|
}
|
|
|
|
explicit operator bool() const { return bool(value.getOpaqueValue()); }
|
|
|
|
Kind getKind() const { return Kind(value.getInt()); }
|
|
|
|
SILValue getValue() const {
|
|
assert(getKind() == Kind::Value);
|
|
return value.getPointer();
|
|
}
|
|
|
|
LLVM_ATTRIBUTE_USED SILValue maybeGetValue() const {
|
|
if (getKind() != Kind::Value)
|
|
return SILValue();
|
|
return getValue();
|
|
}
|
|
|
|
bool isValue() const { return getKind() == Kind::Value; }
|
|
|
|
bool isAccessorInit() const { return getKind() == Kind::ActorAccessorInit; }
|
|
|
|
bool isCapturedActorSelf() const {
|
|
return getKind() == Kind::CapturedActorSelf;
|
|
}
|
|
|
|
bool operator==(const ActorInstance &other) const {
|
|
// If both are null, return true.
|
|
if (!bool(*this) && !bool(other))
|
|
return true;
|
|
|
|
// Otherwise, check if the kinds match.
|
|
if (getKind() != other.getKind())
|
|
return false;
|
|
|
|
// Now that we know that the kinds match, perform the kind specific check.
|
|
switch (getKind()) {
|
|
case Kind::Value:
|
|
return getValue() == other.getValue();
|
|
case Kind::ActorAccessorInit:
|
|
case Kind::CapturedActorSelf:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool operator!=(const ActorInstance &other) const {
|
|
return !(*this == other);
|
|
}
|
|
|
|
void print(llvm::raw_ostream &os) const;
|
|
|
|
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
|
|
};
|
|
|
|
/// The isolation info inferred for a specific SILValue. Use
|
|
/// SILIsolationInfo::get() to compute these. It is intended to be a
|
|
/// conservatively correct model that we expand over time with more pattern
|
|
/// matching.
|
|
class SILIsolationInfo {
|
|
public:
|
|
/// This forms a lattice of semantics. The lattice progresses from left ->
|
|
/// right below:
|
|
///
|
|
/// Unknown -> Disconnected -> Task -> Actor.
|
|
///
|
|
enum Kind : uint8_t {
|
|
/// Unknown means no information. We error when merging on it.
|
|
Unknown,
|
|
|
|
/// An entity with disconnected isolation can be freely sent into another
|
|
/// isolation domain. These are associated with "use after send"
|
|
/// diagnostics.
|
|
Disconnected,
|
|
|
|
/// An entity that is in the same region as a task-isolated value. Cannot be
|
|
/// sent into another isolation domain.
|
|
Task,
|
|
|
|
/// An entity that is in the same region as an actor-isolated value. Cannot
|
|
/// be sent into another isolation domain.
|
|
Actor,
|
|
};
|
|
|
|
enum class Flag : uint8_t {
|
|
None,
|
|
|
|
/// If set, this means that the element that we derived this from was marked
|
|
/// with nonisolated(unsafe).
|
|
UnsafeNonIsolated = 0x1,
|
|
|
|
/// If set, this means that this actor isolation is from an isolated
|
|
/// parameter and should be allowed to merge into a self parameter.
|
|
UnappliedIsolatedAnyParameter = 0x2,
|
|
|
|
/// If set, this was a TaskIsolated value from a nonisolated(nonsending)
|
|
/// parameter.
|
|
NonisolatedNonsendingTaskIsolated = 0x4,
|
|
|
|
/// The maximum number of bits used by a Flag.
|
|
MaxNumBits = 3,
|
|
};
|
|
|
|
using Options = OptionSet<Flag>;
|
|
|
|
private:
|
|
/// The actor isolation if this value has one. The default unspecified case
|
|
/// otherwise.
|
|
ActorIsolation actorIsolation;
|
|
|
|
/// This is the value that we got isolation from if we were able to find
|
|
/// one. Used for isolation history.
|
|
SILValue isolatedValue;
|
|
|
|
/// If set this is the SILValue that represents the actor instance that we
|
|
/// derived isolatedValue from.
|
|
ActorInstance actorInstance;
|
|
|
|
/// When the isolation is introduced due to a (potentially) isolated
|
|
/// conformance, the protocol whose conformance might be isolated.
|
|
ProtocolDecl *isolatedConformance = nullptr;
|
|
|
|
unsigned kind : 8;
|
|
unsigned options : 8;
|
|
|
|
SILIsolationInfo(SILValue isolatedValue, SILValue actorInstance,
|
|
ActorIsolation actorIsolation, Options options = Options(),
|
|
ProtocolDecl *isolatedConformance = nullptr)
|
|
: actorIsolation(actorIsolation), isolatedValue(isolatedValue),
|
|
actorInstance(ActorInstance::getForValue(actorInstance)),
|
|
isolatedConformance(isolatedConformance), kind(Actor),
|
|
options(options.toRaw()) {
|
|
assert((!actorInstance ||
|
|
(actorIsolation.getKind() == ActorIsolation::ActorInstance &&
|
|
actorInstance->getType()
|
|
.getASTType()
|
|
->lookThroughAllOptionalTypes()
|
|
->getAnyActor())) &&
|
|
"actorInstance must be an actor if it is non-empty");
|
|
assert((getKind() != Disconnected || isolatedConformance == nullptr) &&
|
|
"isolated conformance cannot be introduced with disconnected region");
|
|
}
|
|
|
|
SILIsolationInfo(SILValue isolatedValue, ActorInstance actorInstance,
|
|
ActorIsolation actorIsolation, Options options = Options(),
|
|
ProtocolDecl *isolatedConformance = nullptr)
|
|
: actorIsolation(actorIsolation), isolatedValue(isolatedValue),
|
|
actorInstance(actorInstance), isolatedConformance(isolatedConformance),
|
|
kind(Actor), options(options.toRaw())
|
|
{
|
|
assert(actorInstance);
|
|
assert(actorIsolation.getKind() == ActorIsolation::ActorInstance);
|
|
}
|
|
|
|
SILIsolationInfo(Kind kind, SILValue isolatedValue,
|
|
ProtocolDecl *isolatedConformance = nullptr)
|
|
: actorIsolation(), isolatedValue(isolatedValue),
|
|
isolatedConformance(isolatedConformance), kind(kind), options(0) {
|
|
}
|
|
|
|
SILIsolationInfo(Kind kind, Options options = Options())
|
|
: actorIsolation(), kind(kind), options(options.toRaw()) {}
|
|
|
|
/// Infer isolation region from the set of protocol conformances.
|
|
static SILIsolationInfo getFromConformances(
|
|
SILValue value, ArrayRef<ProtocolConformanceRef> conformances);
|
|
|
|
/// Determine the isolation of conformances that could be introduced by a
|
|
/// cast from sourceType to destType.
|
|
static SILIsolationInfo getForCastConformances(
|
|
SILValue value, CanType sourceType, CanType destType);
|
|
|
|
public:
|
|
SILIsolationInfo() : actorIsolation(), kind(Kind::Unknown), options(0) {}
|
|
|
|
operator bool() const { return kind != Kind::Unknown; }
|
|
|
|
operator Kind() const { return getKind(); }
|
|
|
|
Kind getKind() const { return Kind(kind); }
|
|
|
|
bool isDisconnected() const { return kind == Kind::Disconnected; }
|
|
bool isActorIsolated() const { return kind == Kind::Actor; }
|
|
bool isTaskIsolated() const { return kind == Kind::Task; }
|
|
|
|
Options getOptions() const { return Options(options); }
|
|
|
|
void setOptions(Options newOptions) { options = newOptions.toRaw(); }
|
|
|
|
bool isUnsafeNonIsolated() const {
|
|
return getOptions().contains(Flag::UnsafeNonIsolated);
|
|
}
|
|
|
|
// Retrieve the protocol to which there is (or could be) an isolated
|
|
// conformance.
|
|
ProtocolDecl *getIsolatedConformance() const {
|
|
return isolatedConformance;
|
|
}
|
|
|
|
SILIsolationInfo withUnsafeNonIsolated(bool newValue = true) const {
|
|
assert(*this && "Cannot be unknown");
|
|
auto self = *this;
|
|
if (newValue) {
|
|
self.options = (self.getOptions() | Flag::UnsafeNonIsolated).toRaw();
|
|
} else {
|
|
self.options =
|
|
self.getOptions().toRaw() & ~Options(Flag::UnsafeNonIsolated).toRaw();
|
|
}
|
|
return self;
|
|
}
|
|
|
|
bool isNonisolatedNonsendingTaskIsolated() const {
|
|
return getOptions().contains(Flag::NonisolatedNonsendingTaskIsolated);
|
|
}
|
|
|
|
SILIsolationInfo
|
|
withNonisolatedNonsendingTaskIsolated(bool newValue = true) const {
|
|
assert(*this && "Cannot be unknown");
|
|
assert(isTaskIsolated() && "Can only be task isolated");
|
|
auto self = *this;
|
|
if (newValue) {
|
|
self.options =
|
|
(self.getOptions() | Flag::NonisolatedNonsendingTaskIsolated).toRaw();
|
|
} else {
|
|
self.options = self.getOptions().toRaw() &
|
|
~Options(Flag::NonisolatedNonsendingTaskIsolated).toRaw();
|
|
}
|
|
return self;
|
|
}
|
|
|
|
/// Produce a new isolation info value that merges in the given isolated
|
|
/// conformance value.
|
|
///
|
|
/// If both isolation infos have an isolation conformance, pick one
|
|
/// arbitrarily. Otherwise, the result has no isolated conformance.
|
|
SILIsolationInfo
|
|
withMergedIsolatedConformance(ProtocolDecl *newIsolatedConformance) const {
|
|
SILIsolationInfo result(*this);
|
|
if (!isolatedConformance || !newIsolatedConformance) {
|
|
result.isolatedConformance = nullptr;
|
|
return result;
|
|
}
|
|
|
|
result.isolatedConformance =
|
|
ProtocolDecl::compare(isolatedConformance, newIsolatedConformance) <= 0
|
|
? isolatedConformance
|
|
: newIsolatedConformance;
|
|
return result;
|
|
}
|
|
|
|
/// Returns true if this actor isolation is derived from an unapplied
|
|
/// isolation parameter. When merging, we allow for this to be merged with a
|
|
/// more specific isolation kind.
|
|
bool isUnappliedIsolatedAnyParameter() const {
|
|
return getOptions().contains(Flag::UnappliedIsolatedAnyParameter);
|
|
}
|
|
|
|
SILIsolationInfo withUnappliedIsolatedParameter(bool newValue = true) const {
|
|
assert(*this && "Cannot be unknown");
|
|
auto self = *this;
|
|
if (newValue) {
|
|
self.options =
|
|
(self.getOptions() | Flag::UnappliedIsolatedAnyParameter).toRaw();
|
|
} else {
|
|
self.options = self.getOptions().toRaw() &
|
|
~Options(Flag::UnappliedIsolatedAnyParameter).toRaw();
|
|
}
|
|
return self;
|
|
}
|
|
|
|
void print(SILFunction *fn, llvm::raw_ostream &os) const;
|
|
|
|
/// Print a textual representation of the text info that is meant to be
|
|
/// included in other logging output for types that compose with
|
|
/// SILIsolationInfo. As a result, we only print state that can fit on
|
|
/// one line.
|
|
void printForOneLineLogging(SILFunction *fn, llvm::raw_ostream &os) const;
|
|
|
|
SWIFT_DEBUG_DUMPER(dump(SILFunction *fn)) {
|
|
print(fn, llvm::dbgs());
|
|
llvm::dbgs() << '\n';
|
|
}
|
|
|
|
/// Prints out the message for a diagnostic that states that the value is
|
|
/// exposed to a specific code.
|
|
///
|
|
/// We do this programatically since task-isolated code needs a very different
|
|
/// form of diagnostic than other cases.
|
|
void printForCodeDiagnostic(SILFunction *fn, llvm::raw_ostream &os) const;
|
|
|
|
/// Overload of printForCodeDiagnostics that returns an interned StringRef
|
|
/// owned by the AST.
|
|
StringRef printForCodeDiagnostic(SILFunction *fn) const;
|
|
|
|
void printForDiagnostics(SILFunction *fn, llvm::raw_ostream &os) const;
|
|
|
|
/// Overload of printForDiagnostics that returns an interned StringRef owned
|
|
/// by the AST.
|
|
StringRef printForDiagnostics(SILFunction *fn) const;
|
|
|
|
SWIFT_DEBUG_DUMPER(dumpForDiagnostics(SILFunction *fn)) {
|
|
printForDiagnostics(fn, llvm::dbgs());
|
|
llvm::dbgs() << '\n';
|
|
}
|
|
|
|
ActorIsolation getActorIsolation() const {
|
|
assert(kind == Actor);
|
|
return actorIsolation;
|
|
}
|
|
|
|
/// If we are actor or task isolated and could find a specific value that
|
|
/// caused the isolation, put it here. Used for isolation history.
|
|
SILValue getIsolatedValue() const {
|
|
assert(kind == Task || kind == Actor);
|
|
return isolatedValue;
|
|
}
|
|
|
|
/// Return the specific SILValue for the actor that our isolated value is
|
|
/// isolated to if one exists.
|
|
ActorInstance getActorInstance() const {
|
|
assert(kind == Actor);
|
|
return actorInstance;
|
|
}
|
|
|
|
bool hasActorIsolation() const { return kind == Actor; }
|
|
|
|
bool hasIsolatedValue() const {
|
|
return (kind == Task || kind == Actor) && bool(isolatedValue);
|
|
}
|
|
|
|
SILValue maybeGetIsolatedValue() const {
|
|
if (!hasIsolatedValue())
|
|
return {};
|
|
return getIsolatedValue();
|
|
}
|
|
|
|
static SILIsolationInfo getDisconnected(bool isUnsafeNonIsolated) {
|
|
return {Kind::Disconnected,
|
|
isUnsafeNonIsolated ? Flag::UnsafeNonIsolated : Flag::None};
|
|
}
|
|
|
|
/// Create an actor isolation for a value that we know is actor isolated to a
|
|
/// specific actor, but we do not know the specific instance yet.
|
|
///
|
|
/// This can occur when closing over a closure with an isolated parameter or
|
|
/// if we are determining isolation of a function_ref that takes an isolated
|
|
/// parameter. In both cases, we cannot know what the actual isolation is
|
|
/// until we invoke the closure or function.
|
|
///
|
|
/// TODO: This is just a stub currently until I implement the flow sensitive
|
|
/// part. We just treat all instances the same. There are tests that validate
|
|
/// this behavior.
|
|
static SILIsolationInfo
|
|
getFlowSensitiveActorIsolated(SILValue isolatedValue,
|
|
ActorIsolation actorIsolation) {
|
|
return {isolatedValue, SILValue(), actorIsolation,
|
|
Flag::UnappliedIsolatedAnyParameter};
|
|
}
|
|
|
|
static SILIsolationInfo getActorInstanceIsolated(SILValue isolatedValue,
|
|
SILValue actorInstance,
|
|
NominalTypeDecl *typeDecl) {
|
|
assert(actorInstance);
|
|
if (!typeDecl->isAnyActor()) {
|
|
assert(!swift::getActorIsolation(typeDecl).isGlobalActor() &&
|
|
"Should have called getGlobalActorIsolated");
|
|
return {};
|
|
}
|
|
return {isolatedValue, actorInstance,
|
|
ActorIsolation::forActorInstanceSelf(typeDecl)};
|
|
}
|
|
|
|
static SILIsolationInfo
|
|
getActorInstanceIsolated(SILValue isolatedValue,
|
|
const SILFunctionArgument *actorInstance) {
|
|
assert(actorInstance);
|
|
auto *varDecl =
|
|
llvm::dyn_cast_if_present<VarDecl>(actorInstance->getDecl());
|
|
if (!varDecl)
|
|
return {};
|
|
return {isolatedValue, actorInstance,
|
|
actorInstance->isSelf()
|
|
? ActorIsolation::forActorInstanceSelf(varDecl)
|
|
: ActorIsolation::forActorInstanceParameter(
|
|
varDecl, actorInstance->getIndex())};
|
|
}
|
|
|
|
static SILIsolationInfo getActorInstanceIsolated(SILValue isolatedValue,
|
|
ActorInstance actorInstance,
|
|
NominalTypeDecl *typeDecl) {
|
|
assert(actorInstance);
|
|
if (!typeDecl->isAnyActor()) {
|
|
assert(!swift::getActorIsolation(typeDecl).isGlobalActor() &&
|
|
"Should have called getGlobalActorIsolated");
|
|
return {};
|
|
}
|
|
return {isolatedValue, actorInstance,
|
|
ActorIsolation::forActorInstanceSelf(typeDecl)};
|
|
}
|
|
|
|
/// A special actor instance isolated for partial apply cases where we do not
|
|
/// close over the isolated parameter and thus do not know the actual actor
|
|
/// instance that we are going to use.
|
|
static SILIsolationInfo
|
|
getPartialApplyActorInstanceIsolated(SILValue isolatedValue,
|
|
NominalTypeDecl *typeDecl) {
|
|
if (!typeDecl->isAnyActor()) {
|
|
assert(!swift::getActorIsolation(typeDecl).isGlobalActor() &&
|
|
"Should have called getGlobalActorIsolated");
|
|
return {};
|
|
}
|
|
return {isolatedValue, SILValue(),
|
|
ActorIsolation::forActorInstanceSelf(typeDecl),
|
|
Flag::UnappliedIsolatedAnyParameter};
|
|
}
|
|
|
|
static SILIsolationInfo getGlobalActorIsolated(
|
|
SILValue value,
|
|
Type globalActorType,
|
|
ProtocolDecl *isolatedConformance = nullptr) {
|
|
return {value, SILValue() /*no actor instance*/,
|
|
ActorIsolation::forGlobalActor(globalActorType),
|
|
Options(), isolatedConformance};
|
|
}
|
|
|
|
static SILIsolationInfo getGlobalActorIsolated(SILValue value,
|
|
ValueDecl *decl) {
|
|
auto isolation = swift::getActorIsolation(decl);
|
|
if (!isolation.isGlobalActor())
|
|
return {};
|
|
return SILIsolationInfo::getGlobalActorIsolated(value,
|
|
isolation.getGlobalActor());
|
|
}
|
|
|
|
static SILIsolationInfo getTaskIsolated(
|
|
SILValue value, ProtocolDecl *isolatedConformance = nullptr) {
|
|
return {Kind::Task, value, isolatedConformance};
|
|
}
|
|
|
|
/// Attempt to infer the isolation region info for \p inst.
|
|
static SILIsolationInfo get(SILInstruction *inst);
|
|
|
|
/// Attempt to infer the isolation region info for \p arg.
|
|
static SILIsolationInfo get(SILArgument *arg);
|
|
|
|
static SILIsolationInfo get(SILValue value) {
|
|
if (auto *arg = dyn_cast<SILArgument>(value))
|
|
return get(arg);
|
|
if (auto *inst = dyn_cast<SingleValueInstruction>(value))
|
|
return get(inst);
|
|
return {};
|
|
}
|
|
|
|
/// Infer isolation of conformances for the given instruction.
|
|
static SILIsolationInfo getConformanceIsolation(SILInstruction *inst);
|
|
|
|
/// Return SILIsolationInfo based off of the attached ActorIsolation of \p
|
|
/// fn. If \p fn does not have an actor isolation set, returns an invalid
|
|
/// SILIsolationInfo.
|
|
static SILIsolationInfo getFunctionIsolation(SILFunction *fn);
|
|
|
|
/// A helper that is used to ensure that we treat certain builtin values as
|
|
/// non-Sendable that the AST level otherwise thinks are non-Sendable.
|
|
///
|
|
/// E.x.: Builtin.RawPointer and Builtin.NativeObject
|
|
///
|
|
/// TODO: Fix the type checker.
|
|
static bool isNonSendableType(SILType type, SILFunction *fn);
|
|
|
|
static bool isSendableType(SILType type, SILFunction *fn) {
|
|
return !isNonSendableType(type, fn);
|
|
}
|
|
|
|
static bool isNonSendableType(SILValue value) {
|
|
return isNonSendableType(value->getType(), value->getFunction());
|
|
}
|
|
|
|
static bool isSendableType(SILValue value) {
|
|
return !isNonSendableType(value);
|
|
}
|
|
|
|
bool hasSameIsolation(ActorIsolation actorIsolation) const;
|
|
|
|
/// Returns true if \p this and \p other have the same isolation. It allows
|
|
/// for the isolated values if any to not match.
|
|
///
|
|
/// This is useful if one has two non-Sendable values projected from the same
|
|
/// actor or global-actor-isolated value. E.x.: two different ref_element_addr
|
|
/// from the same actor.
|
|
bool hasSameIsolation(const SILIsolationInfo &other) const;
|
|
|
|
/// Returns true if this SILIsolationInfo is deeply equal to other. This means
|
|
/// that the isolation and the isolated value match.
|
|
bool isEqual(const SILIsolationInfo &other) const;
|
|
|
|
/// A helper function that prints ActorIsolation like we normally do except
|
|
/// that it prints nonisolated(nonsending) as nonisolated. This is needed in
|
|
/// certain cases when talking about use-after-free uses in send non sendable.
|
|
static void printActorIsolationForDiagnostics(
|
|
SILFunction *fn, ActorIsolation iso, llvm::raw_ostream &os,
|
|
StringRef openingQuotationMark = "'", bool asNoun = false);
|
|
|
|
/// Overload for printActorIsolationForDiagnostics that produces a StringRef.
|
|
static StringRef
|
|
printActorIsolationForDiagnostics(SILFunction *fn, ActorIsolation iso,
|
|
StringRef openingQuotationMark = "'",
|
|
bool asNoun = false);
|
|
|
|
void Profile(llvm::FoldingSetNodeID &id) const;
|
|
|
|
private:
|
|
void printOptions(llvm::raw_ostream &os) const;
|
|
|
|
/// This is used only to let through apply isolation crossings that we define
|
|
/// in SIL just for testing. Do not use this in any other contexts!
|
|
static SILIsolationInfo
|
|
getWithIsolationCrossing(ApplyIsolationCrossing crossing) {
|
|
if (!crossing.getCalleeIsolation().isActorIsolated())
|
|
return {};
|
|
|
|
// SIL level, just let it through without an actor instance. We assume since
|
|
// we are using this for SIL tests that we do not need to worry about having
|
|
// a null actor instance.
|
|
return SILIsolationInfo(SILValue(), SILValue(),
|
|
crossing.getCalleeIsolation());
|
|
}
|
|
};
|
|
|
|
/// A SILIsolationInfo that has gone through merging and represents the dynamic
|
|
/// isolation info of a value found by merging its isolation at a region
|
|
/// point. This means that nonisolated(unsafe) has been removed. It is used so
|
|
/// that in the type system we can distinguish in between isolation info that is
|
|
/// static isolation info associated with a value and dynamic isolation info
|
|
/// that can just be used for dataflow.
|
|
class SILDynamicMergedIsolationInfo {
|
|
SILIsolationInfo innerInfo;
|
|
|
|
public:
|
|
SILDynamicMergedIsolationInfo() : innerInfo() {}
|
|
SILDynamicMergedIsolationInfo(SILIsolationInfo innerInfo)
|
|
: innerInfo(innerInfo) {}
|
|
|
|
/// Returns nullptr only if both this isolation info and \p other are actor
|
|
/// isolated to incompatible actors.
|
|
[[nodiscard]] std::optional<SILDynamicMergedIsolationInfo>
|
|
merge(SILIsolationInfo other) const;
|
|
|
|
[[nodiscard]] std::optional<SILDynamicMergedIsolationInfo>
|
|
merge(SILDynamicMergedIsolationInfo other) const {
|
|
return merge(other.getIsolationInfo());
|
|
}
|
|
|
|
operator bool() const { return bool(innerInfo); }
|
|
|
|
SILIsolationInfo *operator->() { return &innerInfo; }
|
|
const SILIsolationInfo *operator->() const { return &innerInfo; }
|
|
|
|
SILIsolationInfo getIsolationInfo() const { return innerInfo; }
|
|
|
|
bool isDisconnected() const { return innerInfo.isDisconnected(); }
|
|
|
|
bool hasSameIsolation(SILIsolationInfo other) const {
|
|
return innerInfo.hasSameIsolation(other);
|
|
}
|
|
|
|
static SILDynamicMergedIsolationInfo
|
|
getDisconnected(bool isUnsafeNonIsolated) {
|
|
return SILDynamicMergedIsolationInfo(
|
|
SILIsolationInfo::getDisconnected(isUnsafeNonIsolated));
|
|
}
|
|
|
|
SWIFT_DEBUG_DUMPER(dump(SILFunction *fn)) { innerInfo.dump(fn); }
|
|
|
|
void printForDiagnostics(SILFunction *fn, llvm::raw_ostream &os) const {
|
|
innerInfo.printForDiagnostics(fn, os);
|
|
}
|
|
|
|
StringRef printForDiagnostics(SILFunction *fn) const {
|
|
return innerInfo.printForDiagnostics(fn);
|
|
}
|
|
|
|
SWIFT_DEBUG_DUMPER(dumpForDiagnostics(SILFunction *fn)) {
|
|
innerInfo.dumpForDiagnostics(fn);
|
|
}
|
|
|
|
void printForCodeDiagnostic(SILFunction *fn, llvm::raw_ostream &os) const {
|
|
innerInfo.printForCodeDiagnostic(fn, os);
|
|
}
|
|
|
|
StringRef printForCodeDiagnostic(SILFunction *fn) const {
|
|
return innerInfo.printForCodeDiagnostic(fn);
|
|
}
|
|
|
|
void printForOneLineLogging(SILFunction *fn, llvm::raw_ostream &os) const {
|
|
innerInfo.printForOneLineLogging(fn, os);
|
|
}
|
|
};
|
|
|
|
} // namespace swift
|
|
|
|
#endif
|