add an experimental feature DeferSendableChecking to defer the sendable checking of some sites. For now, only diagnostics corresponding to non-sendable arguments passed to calls with unsatisfied isolation are deferred. A SIL pass SendNonSendable is added to emit the deferred diagnostics, and ApplyExpr is appropriately enriched to make that deferral possible.

This commit is contained in:
jturcotti
2023-06-30 11:57:13 -07:00
parent cb61903d8c
commit aa9f1a3584
12 changed files with 342 additions and 60 deletions

View File

@@ -4625,6 +4625,41 @@ public:
}
};
// 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 caller 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 {
@@ -4634,13 +4669,14 @@ class ApplyExpr : public Expr {
/// The list of arguments to call the function with.
ArgumentList *ArgList;
ActorIsolation implicitActorHopTarget;
// If this apply crosses isolation boundaries, record the callee and caller
// isolations in this struct.
llvm::Optional<ApplyIsolationCrossing> IsolationCrossing;
protected:
ApplyExpr(ExprKind kind, Expr *fn, ArgumentList *argList, bool implicit,
Type ty = Type())
: Expr(kind, implicit, ty), Fn(fn), ArgList(argList),
implicitActorHopTarget(ActorIsolation::forUnspecified()) {
: Expr(kind, implicit, ty), Fn(fn), ArgList(argList) {
assert(ArgList);
assert(classof((Expr*)this) && "ApplyExpr::classof out of date");
Bits.ApplyExpr.ThrowsIsSet = false;
@@ -4687,6 +4723,21 @@ public:
Bits.ApplyExpr.NoAsync = noAsync;
}
// Return the optionally stored ApplyIsolationCrossing instance - set iff this
// ApplyExpr crosses isolation domains
const llvm::Optional<ApplyIsolationCrossing> getIsolationCrossing() const {
return IsolationCrossing;
}
// Record that this apply crosses isolation domains, noting the isolation of
// the caller and callee by storing them into the IsolationCrossing field
void setIsolationCrossing(
ActorIsolation callerIsolation, ActorIsolation calleeIsolation) {
assert(!IsolationCrossing.has_value()
&& "IsolationCrossing should not be set twice");
IsolationCrossing = {callerIsolation, calleeIsolation};
}
/// Is this application _implicitly_ required to be an async call?
/// That is, does it need to be guarded by hop_to_executor.
/// Note that this is _not_ a check for whether the callee is async!
@@ -4710,13 +4761,20 @@ public:
if (!Bits.ApplyExpr.ImplicitlyAsync)
return llvm::None;
return implicitActorHopTarget;
auto isolationCrossing = getIsolationCrossing();
assert(isolationCrossing.has_value()
&& "Implicitly async ApplyExprs should always "
"have had IsolationCrossing set");
return isolationCrossing.value().CalleeIsolation;
}
/// Note that this application is implicitly async and set the target.
void setImplicitlyAsync(ActorIsolation target) {
assert(getIsolationCrossing().has_value()
&& "ApplyExprs should always call setIsolationCrossing"
" before setImplicitlyAsync");
Bits.ApplyExpr.ImplicitlyAsync = true;
implicitActorHopTarget = target;
}
/// Is this application _implicitly_ required to be a throwing call?

View File

@@ -217,6 +217,9 @@ EXPERIMENTAL_FEATURE(BuiltinModule, true)
// Enable strict concurrency.
EXPERIMENTAL_FEATURE(StrictConcurrency, true)
/// Defer Sendable checking to SIL diagnostic phase.
EXPERIMENTAL_FEATURE(DeferredSendableChecking, false)
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
#undef EXPERIMENTAL_FEATURE
#undef UPCOMING_FEATURE

View File

@@ -365,6 +365,8 @@ PASS(TempRValueOpt, "temp-rvalue-opt",
"Remove short-lived immutable temporary copies")
PASS(IRGenPrepare, "irgen-prepare",
"Cleanup SIL in preparation for IRGen")
PASS(SendNonSendable, "send-non-sendable",
"Checks calls that send non-sendable values between isolation domains")
PASS(SILGenCleanup, "silgen-cleanup",
"Cleanup SIL in preparation for diagnostics")
PASS(SILCombine, "sil-combine",