Skip to content

[region-isolation] Add the ability to write SIL tests for actor isolation #70394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion docs/SIL.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5927,11 +5927,28 @@ Function Application
These instructions call functions or wrap them in partial application or
specialization thunks.

In the following we allow for `apply`_, `begin_apply`_, and `try_apply`_ to have
a callee or caller actor isolation attached to them::

sil-actor-isolation ::= unspecified
::= actor_instance
::= nonisolated
::= nonisolated_unsafe
::= global_actor
::= global_actor_unsafe

sil-actor-isolation-callee ::= [callee_isolation=sil-actor-isolation]
sil-actor-isolation-caller ::= [caller_isolation=sil-actor-isolation]

These can be used to write test cases with actor isolation using these
instructions and is not intended to be used in SILGen today.

apply
`````
::

sil-instruction ::= 'apply' '[nothrow]'? sil-value
sil-instruction ::= 'apply' '[nothrow]'? sil-actor-isolation-callee?
sil-actor-isolation-caller? sil-value
sil-apply-substitution-list?
'(' (sil-value (',' sil-value)*)? ')'
':' sil-type
Expand Down
113 changes: 109 additions & 4 deletions include/swift/AST/ActorIsolation.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

#include "swift/AST/Type.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"

namespace llvm {
class raw_ostream;
Expand Down Expand Up @@ -80,21 +83,25 @@ class ActorIsolation {
};
unsigned kind : 3;
unsigned isolatedByPreconcurrency : 1;
unsigned parameterIndex : 28;

/// Set to true if this was parsed from SIL.
unsigned silParsed : 1;

unsigned parameterIndex : 27;

ActorIsolation(Kind kind, NominalTypeDecl *actor, unsigned parameterIndex);

ActorIsolation(Kind kind, VarDecl *capturedActor);

ActorIsolation(Kind kind, Type globalActor)
: globalActor(globalActor), kind(kind), isolatedByPreconcurrency(false),
parameterIndex(0) { }
silParsed(false), parameterIndex(0) {}

public:
// No-argument constructor needed for DenseMap use in PostfixCompletion.cpp
explicit ActorIsolation(Kind kind = Unspecified)
explicit ActorIsolation(Kind kind = Unspecified, bool isSILParsed = false)
: pointer(nullptr), kind(kind), isolatedByPreconcurrency(false),
parameterIndex(0) { }
silParsed(isSILParsed), parameterIndex(0) {}

static ActorIsolation forUnspecified() {
return ActorIsolation(Unspecified, nullptr);
Expand Down Expand Up @@ -122,6 +129,27 @@ class ActorIsolation {
unsafe ? GlobalActorUnsafe : GlobalActor, globalActor);
}

static std::optional<ActorIsolation> forSILString(StringRef string) {
auto kind =
llvm::StringSwitch<std::optional<ActorIsolation::Kind>>(string)
.Case("unspecified",
std::optional<ActorIsolation>(ActorIsolation::Unspecified))
.Case("actor_instance",
std::optional<ActorIsolation>(ActorIsolation::ActorInstance))
.Case("nonisolated",
std::optional<ActorIsolation>(ActorIsolation::Nonisolated))
.Case("nonisolated_unsafe", std::optional<ActorIsolation>(
ActorIsolation::NonisolatedUnsafe))
.Case("global_actor",
std::optional<ActorIsolation>(ActorIsolation::GlobalActor))
.Case("global_actor_unsafe", std::optional<ActorIsolation>(
ActorIsolation::GlobalActorUnsafe))
.Default(std::nullopt);
if (kind == std::nullopt)
return std::nullopt;
return ActorIsolation(*kind, true /*is sil parsed*/);
}

Kind getKind() const { return (Kind)kind; }

operator Kind() const { return getKind(); }
Expand All @@ -140,6 +168,8 @@ class ActorIsolation {
return parameterIndex;
}

bool isSILParsed() const { return silParsed; }

bool isActorIsolated() const {
switch (getKind()) {
case ActorInstance:
Expand Down Expand Up @@ -168,6 +198,10 @@ class ActorIsolation {

Type getGlobalActor() const {
assert(isGlobalActor());

if (silParsed)
return Type();

return globalActor;
}

Expand Down Expand Up @@ -222,6 +256,32 @@ class ActorIsolation {
state.kind, state.pointer, state.isolatedByPreconcurrency,
state.parameterIndex);
}

void print(llvm::raw_ostream &os) const {
switch (getKind()) {
case Unspecified:
os << "unspecified";
return;
case ActorInstance:
os << "actor_instance";
return;
case Nonisolated:
os << "nonisolated";
return;
case NonisolatedUnsafe:
os << "nonisolated_unsafe";
return;
case GlobalActor:
os << "global_actor";
return;
case GlobalActorUnsafe:
os << "global_actor_unsafe";
return;
}
llvm_unreachable("Covered switch isn't covered?!");
}

SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
};

/// Determine how the given value declaration is isolated.
Expand Down Expand Up @@ -249,6 +309,51 @@ bool usesFlowSensitiveIsolation(AbstractFunctionDecl const *fn);

void simple_display(llvm::raw_ostream &out, const ActorIsolation &state);

// ApplyIsolationCrossing records the source and target of an isolation crossing
// within an ApplyExpr. In particular, it stores the isolation of the caller
// and the callee of the ApplyExpr, to be used for inserting implicit actor
// hops for implicitly async functions and to be used for diagnosing potential
// data races that could arise when non-Sendable values are passed to calls
// that cross isolation domains.
struct ApplyIsolationCrossing {
ActorIsolation CallerIsolation;
ActorIsolation CalleeIsolation;

ApplyIsolationCrossing()
: CallerIsolation(ActorIsolation::forUnspecified()),
CalleeIsolation(ActorIsolation::forUnspecified()) {}

ApplyIsolationCrossing(ActorIsolation CallerIsolation,
ActorIsolation CalleeIsolation)
: CallerIsolation(CallerIsolation), CalleeIsolation(CalleeIsolation) {}

// If the callee is not actor isolated, then this crossing exits isolation.
// This method returns true iff this crossing exits isolation.
bool exitsIsolation() const { return !CalleeIsolation.isActorIsolated(); }

// Whether to use the isolation of the caller or callee for generating
// informative diagnostics depends on whether this crossing is an exit.
// In particular, we tend to use the callee isolation for diagnostics,
// but if this crossing is an exit from isolation then the callee isolation
// is not very informative, so we use the caller isolation instead.
ActorIsolation getDiagnoseIsolation() const {
return exitsIsolation() ? CallerIsolation : CalleeIsolation;
}

ActorIsolation getCallerIsolation() const { return CallerIsolation; }
ActorIsolation getCalleeIsolation() const { return CalleeIsolation; }
};

} // end namespace swift

namespace llvm {

inline llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
const swift::ActorIsolation &other) {
other.print(os);
return os;
}

} // namespace llvm

#endif /* SWIFT_AST_ACTORISOLATIONSTATE_H */
35 changes: 0 additions & 35 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -4639,41 +4639,6 @@ class DefaultArgumentExpr final : public Expr {
}
};

// ApplyIsolationCrossing records the source and target of an isolation crossing
// within an ApplyExpr. In particular, it stores the isolation of the caller
// and the callee of the ApplyExpr, to be used for inserting implicit actor
// hops for implicitly async functions and to be used for diagnosing potential
// data races that could arise when non-Sendable values are passed to calls
// that cross isolation domains.
struct ApplyIsolationCrossing {
ActorIsolation CallerIsolation;
ActorIsolation CalleeIsolation;

ApplyIsolationCrossing()
: CallerIsolation(ActorIsolation::forUnspecified()),
CalleeIsolation(ActorIsolation::forUnspecified()) {}

ApplyIsolationCrossing(ActorIsolation CallerIsolation,
ActorIsolation CalleeIsolation)
: CallerIsolation(CallerIsolation), CalleeIsolation(CalleeIsolation) {}

// If the callee is not actor isolated, then this crossing exits isolation.
// This method returns true iff this crossing exits isolation.
bool exitsIsolation() const { return !CalleeIsolation.isActorIsolated(); }

// Whether to use the isolation of the caller or callee for generating
// informative diagnostics depends on whether this crossing is an exit.
// In particular, we tend to use the callee isolation for diagnostics,
// but if this crossing is an exit from isolation then the callee isolation
// is not very informative, so we use the caller isolation instead.
ActorIsolation getDiagnoseIsolation() const {
return exitsIsolation() ? CallerIsolation : CalleeIsolation;
}

ActorIsolation getCallerIsolation() const { return CallerIsolation; }
ActorIsolation getCalleeIsolation() const {return CalleeIsolation; }
};

/// ApplyExpr - Superclass of various function calls, which apply an argument to
/// a function to get a result.
class ApplyExpr : public Expr {
Expand Down
11 changes: 11 additions & 0 deletions include/swift/SIL/ApplySite.h
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,17 @@ class FullApplySite : public ApplySite {
&& (getCalleeArgIndex(op) < getNumIndirectSILResults() + getNumIndirectSILErrorResults());
}

std::optional<ApplyIsolationCrossing> getIsolationCrossing() const {
switch (getKind()) {
case FullApplySiteKind::ApplyInst:
return cast<ApplyInst>(**this)->getIsolationCrossing();
case FullApplySiteKind::TryApplyInst:
return cast<TryApplyInst>(**this)->getIsolationCrossing();
case FullApplySiteKind::BeginApplyInst:
return cast<BeginApplyInst>(**this)->getIsolationCrossing();
}
}

static FullApplySite getFromOpaqueValue(void *p) { return FullApplySite(p); }

static bool classof(const SILInstruction *inst) {
Expand Down
34 changes: 18 additions & 16 deletions include/swift/SIL/SILBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -523,23 +523,24 @@ class SILBuilder {
}

ApplyInst *createApply(
SILLocation Loc, SILValue Fn, SubstitutionMap Subs,
ArrayRef<SILValue> Args,
ApplyOptions options,
const GenericSpecializationInformation *SpecializationInfo = nullptr) {
return insert(ApplyInst::create(getSILDebugLocation(Loc), Fn, Subs, Args,
options, C.silConv, *F,
SpecializationInfo));
SILLocation loc, SILValue callee, SubstitutionMap subs,
ArrayRef<SILValue> args, ApplyOptions options,
const GenericSpecializationInformation *specializationInfo = nullptr,
std::optional<ApplyIsolationCrossing> isolationCrossing = std::nullopt) {
return insert(ApplyInst::create(getSILDebugLocation(loc), callee, subs,
args, options, C.silConv, *F,
specializationInfo, isolationCrossing));
}

TryApplyInst *createTryApply(
SILLocation Loc, SILValue fn, SubstitutionMap subs,
SILLocation loc, SILValue callee, SubstitutionMap subs,
ArrayRef<SILValue> args, SILBasicBlock *normalBB, SILBasicBlock *errorBB,
ApplyOptions options = ApplyOptions(),
const GenericSpecializationInformation *SpecializationInfo = nullptr) {
const GenericSpecializationInformation *specializationInfo = nullptr,
std::optional<ApplyIsolationCrossing> isolationCrossing = std::nullopt) {
return insertTerminator(TryApplyInst::create(
getSILDebugLocation(Loc), fn, subs, args, normalBB, errorBB,
options, *F, SpecializationInfo));
getSILDebugLocation(loc), callee, subs, args, normalBB, errorBB,
options, *F, specializationInfo, isolationCrossing));
}

PartialApplyInst *createPartialApply(
Expand All @@ -561,12 +562,13 @@ class SILBuilder {
}

BeginApplyInst *createBeginApply(
SILLocation Loc, SILValue Fn, SubstitutionMap Subs,
ArrayRef<SILValue> Args, ApplyOptions options = ApplyOptions(),
const GenericSpecializationInformation *SpecializationInfo = nullptr) {
SILLocation loc, SILValue callee, SubstitutionMap subs,
ArrayRef<SILValue> args, ApplyOptions options = ApplyOptions(),
const GenericSpecializationInformation *specializationInfo = nullptr,
std::optional<ApplyIsolationCrossing> isolationCrossing = std::nullopt) {
return insert(BeginApplyInst::create(
getSILDebugLocation(Loc), Fn, Subs, Args, options, C.silConv, *F,
SpecializationInfo));
getSILDebugLocation(loc), callee, subs, args, options, C.silConv, *F,
specializationInfo, isolationCrossing));
}

AbortApplyInst *createAbortApply(SILLocation loc, SILValue beginApply) {
Expand Down
Loading