Skip to content

Commit 300e7ce

Browse files
author
Joshua Turcotti
authored
Merge pull request #67059 from JTurcotti/defer-sil
Add experimental feature DeferSendableChecking to defer some Sendable checks to a SIL pass. Lays the groundwork for more sophisticated flow-sensitive linear Sendable checking in future PRs.
2 parents 80e4e04 + aa9f1a3 commit 300e7ce

File tree

12 files changed

+342
-60
lines changed

12 files changed

+342
-60
lines changed

include/swift/AST/Expr.h

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4625,6 +4625,41 @@ class DefaultArgumentExpr final : public Expr {
46254625
}
46264626
};
46274627

4628+
// ApplyIsolationCrossing records the source and target of an isolation crossing
4629+
// within an ApplyExpr. In particular, it stores the isolation of the caller
4630+
// and the callee of the ApplyExpr, to be used for inserting implicit actor
4631+
// hops for implicitly async functions and to be used for diagnosing potential
4632+
// data races that could arise when non-Sendable values are passed to calls
4633+
// that cross isolation domains.
4634+
struct ApplyIsolationCrossing {
4635+
ActorIsolation CallerIsolation;
4636+
ActorIsolation CalleeIsolation;
4637+
4638+
ApplyIsolationCrossing()
4639+
: CallerIsolation(ActorIsolation::forUnspecified()),
4640+
CalleeIsolation(ActorIsolation::forUnspecified()) {}
4641+
4642+
ApplyIsolationCrossing(ActorIsolation CallerIsolation,
4643+
ActorIsolation CalleeIsolation)
4644+
: CallerIsolation(CallerIsolation), CalleeIsolation(CalleeIsolation) {}
4645+
4646+
// If the callee is not actor isolated, then this crossing exits isolation.
4647+
// This method returns true iff this crossing exits isolation.
4648+
bool exitsIsolation() const { return !CalleeIsolation.isActorIsolated(); }
4649+
4650+
// Whether to use the isolation of the caller or callee for generating
4651+
// informative diagnostics depends on whether this crossing is an exit.
4652+
// In particular, we tend to use the caller isolation for diagnostics,
4653+
// but if this crossing is an exit from isolation then the callee isolation
4654+
// is not very informative, so we use the caller isolation instead.
4655+
ActorIsolation getDiagnoseIsolation() const {
4656+
return exitsIsolation() ? CallerIsolation : CalleeIsolation;
4657+
}
4658+
4659+
ActorIsolation getCallerIsolation() const { return CallerIsolation; }
4660+
ActorIsolation getCalleeIsolation() const {return CalleeIsolation; }
4661+
};
4662+
46284663
/// ApplyExpr - Superclass of various function calls, which apply an argument to
46294664
/// a function to get a result.
46304665
class ApplyExpr : public Expr {
@@ -4634,13 +4669,14 @@ class ApplyExpr : public Expr {
46344669
/// The list of arguments to call the function with.
46354670
ArgumentList *ArgList;
46364671

4637-
ActorIsolation implicitActorHopTarget;
4672+
// If this apply crosses isolation boundaries, record the callee and caller
4673+
// isolations in this struct.
4674+
llvm::Optional<ApplyIsolationCrossing> IsolationCrossing;
46384675

46394676
protected:
46404677
ApplyExpr(ExprKind kind, Expr *fn, ArgumentList *argList, bool implicit,
46414678
Type ty = Type())
4642-
: Expr(kind, implicit, ty), Fn(fn), ArgList(argList),
4643-
implicitActorHopTarget(ActorIsolation::forUnspecified()) {
4679+
: Expr(kind, implicit, ty), Fn(fn), ArgList(argList) {
46444680
assert(ArgList);
46454681
assert(classof((Expr*)this) && "ApplyExpr::classof out of date");
46464682
Bits.ApplyExpr.ThrowsIsSet = false;
@@ -4687,6 +4723,21 @@ class ApplyExpr : public Expr {
46874723
Bits.ApplyExpr.NoAsync = noAsync;
46884724
}
46894725

4726+
// Return the optionally stored ApplyIsolationCrossing instance - set iff this
4727+
// ApplyExpr crosses isolation domains
4728+
const llvm::Optional<ApplyIsolationCrossing> getIsolationCrossing() const {
4729+
return IsolationCrossing;
4730+
}
4731+
4732+
// Record that this apply crosses isolation domains, noting the isolation of
4733+
// the caller and callee by storing them into the IsolationCrossing field
4734+
void setIsolationCrossing(
4735+
ActorIsolation callerIsolation, ActorIsolation calleeIsolation) {
4736+
assert(!IsolationCrossing.has_value()
4737+
&& "IsolationCrossing should not be set twice");
4738+
IsolationCrossing = {callerIsolation, calleeIsolation};
4739+
}
4740+
46904741
/// Is this application _implicitly_ required to be an async call?
46914742
/// That is, does it need to be guarded by hop_to_executor.
46924743
/// Note that this is _not_ a check for whether the callee is async!
@@ -4710,13 +4761,20 @@ class ApplyExpr : public Expr {
47104761
if (!Bits.ApplyExpr.ImplicitlyAsync)
47114762
return llvm::None;
47124763

4713-
return implicitActorHopTarget;
4764+
auto isolationCrossing = getIsolationCrossing();
4765+
assert(isolationCrossing.has_value()
4766+
&& "Implicitly async ApplyExprs should always "
4767+
"have had IsolationCrossing set");
4768+
4769+
return isolationCrossing.value().CalleeIsolation;
47144770
}
47154771

47164772
/// Note that this application is implicitly async and set the target.
47174773
void setImplicitlyAsync(ActorIsolation target) {
4774+
assert(getIsolationCrossing().has_value()
4775+
&& "ApplyExprs should always call setIsolationCrossing"
4776+
" before setImplicitlyAsync");
47184777
Bits.ApplyExpr.ImplicitlyAsync = true;
4719-
implicitActorHopTarget = target;
47204778
}
47214779

47224780
/// Is this application _implicitly_ required to be a throwing call?

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ EXPERIMENTAL_FEATURE(BuiltinModule, true)
217217
// Enable strict concurrency.
218218
EXPERIMENTAL_FEATURE(StrictConcurrency, true)
219219

220+
/// Defer Sendable checking to SIL diagnostic phase.
221+
EXPERIMENTAL_FEATURE(DeferredSendableChecking, false)
222+
220223
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
221224
#undef EXPERIMENTAL_FEATURE
222225
#undef UPCOMING_FEATURE

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,8 @@ PASS(TempRValueOpt, "temp-rvalue-opt",
365365
"Remove short-lived immutable temporary copies")
366366
PASS(IRGenPrepare, "irgen-prepare",
367367
"Cleanup SIL in preparation for IRGen")
368+
PASS(SendNonSendable, "send-non-sendable",
369+
"Checks calls that send non-sendable values between isolation domains")
368370
PASS(SILGenCleanup, "silgen-cleanup",
369371
"Cleanup SIL in preparation for diagnostics")
370372
PASS(SILCombine, "sil-combine",

lib/AST/ASTDumper.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,6 +2715,25 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
27152715
PrintWithColorRAII(OS, ExprModifierColor)
27162716
<< (E->throws() ? " throws" : " nothrow");
27172717
}
2718+
PrintWithColorRAII(OS, ExprModifierColor)
2719+
<< " isolationCrossing=";
2720+
auto isolationCrossing = E->getIsolationCrossing();
2721+
if (isolationCrossing.has_value()) {
2722+
PrintWithColorRAII(OS, ExprModifierColor)
2723+
<< "{caller='";
2724+
simple_display(PrintWithColorRAII(OS, ExprModifierColor).getOS(),
2725+
isolationCrossing.value().getCallerIsolation());
2726+
PrintWithColorRAII(OS, ExprModifierColor)
2727+
<< "', callee='";
2728+
simple_display(PrintWithColorRAII(OS, ExprModifierColor).getOS(),
2729+
isolationCrossing.value().getCalleeIsolation());
2730+
2731+
PrintWithColorRAII(OS, ExprModifierColor)
2732+
<< "'}";
2733+
} else {
2734+
PrintWithColorRAII(OS, ExprModifierColor)
2735+
<< "none";
2736+
}
27182737
OS << '\n';
27192738
printRec(E->getFn());
27202739
OS << '\n';

lib/AST/ASTPrinter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3465,6 +3465,10 @@ static bool usesFeatureParameterPacks(Decl *decl) {
34653465
return false;
34663466
}
34673467

3468+
static bool usesFeatureDeferredSendableChecking(Decl *decl) {
3469+
return false;
3470+
}
3471+
34683472
/// Suppress the printing of a particular feature.
34693473
static void suppressingFeature(PrintOptions &options, Feature feature,
34703474
llvm::function_ref<void()> action) {

lib/SILOptimizer/Mandatory/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ target_sources(swiftSILOptimizer PRIVATE
4242
PMOMemoryUseCollector.cpp
4343
RawSILInstLowering.cpp
4444
ReferenceBindingTransform.cpp
45+
SendNonSendable.cpp
4546
SILGenCleanup.cpp
4647
YieldOnceCheck.cpp
4748
OSLogOptimization.cpp
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#include "../../Sema/TypeCheckConcurrency.h"
2+
#include "swift/AST/Expr.h"
3+
#include "swift/SILOptimizer/PassManager/Transforms.h"
4+
5+
using namespace swift;
6+
7+
class SendNonSendable : public SILFunctionTransform {
8+
9+
// find any ApplyExprs in this function, and check if any of them make an
10+
// unsatisfied isolation jump, emitting appropriate diagnostics if so
11+
void run() override {
12+
SILFunction *function = getFunction();
13+
14+
if (!function->getASTContext().LangOpts
15+
.hasFeature(Feature::DeferredSendableChecking))
16+
return;
17+
18+
DeclContext *declContext = function->getDeclContext();
19+
20+
for (SILBasicBlock &bb : *function) {
21+
for (SILInstruction &instr : bb) {
22+
if (ApplyExpr *apply = instr.getLoc().getAsASTNode<ApplyExpr>()) {
23+
diagnoseApplyArgSendability(apply, declContext);
24+
}
25+
}
26+
}
27+
}
28+
};
29+
30+
/// This pass is known to depend on the following passes having run before it:
31+
/// none so far
32+
SILTransform *swift::createSendNonSendable() {
33+
return new SendNonSendable();
34+
}

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
132132
P.addAddressLowering();
133133

134134
P.addFlowIsolation();
135+
P.addSendNonSendable();
135136

136137
// Automatic differentiation: canonicalize all differentiability witnesses
137138
// and `differentiable_function` instructions.

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 78 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,6 +1785,75 @@ static void noteGlobalActorOnContext(DeclContext *dc, Type globalActor) {
17851785
}
17861786
}
17871787

1788+
/// Find the original type of a value, looking through various implicit
1789+
/// conversions.
1790+
static Type findOriginalValueType(Expr *expr) {
1791+
do {
1792+
expr = expr->getSemanticsProvidingExpr();
1793+
1794+
if (auto inout = dyn_cast<InOutExpr>(expr)) {
1795+
expr = inout->getSubExpr();
1796+
continue;
1797+
}
1798+
1799+
if (auto ice = dyn_cast<ImplicitConversionExpr>(expr)) {
1800+
expr = ice->getSubExpr();
1801+
continue;
1802+
}
1803+
1804+
if (auto open = dyn_cast<OpenExistentialExpr>(expr)) {
1805+
expr = open->getSubExpr();
1806+
continue;
1807+
}
1808+
1809+
break;
1810+
} while (true);
1811+
1812+
return expr->getType()->getRValueType();
1813+
}
1814+
1815+
bool swift::diagnoseApplyArgSendability(ApplyExpr *apply, const DeclContext *declContext) {
1816+
auto isolationCrossing = apply->getIsolationCrossing();
1817+
if (!isolationCrossing.has_value())
1818+
return false;
1819+
1820+
auto fnExprType = apply->getFn()->getType();
1821+
if (!fnExprType)
1822+
return false;
1823+
1824+
auto fnType = fnExprType->getAs<FunctionType>();
1825+
if (!fnType)
1826+
return false;
1827+
1828+
auto params = fnType->getParams();
1829+
for (unsigned paramIdx : indices(params)) {
1830+
const auto &param = params[paramIdx];
1831+
1832+
// Dig out the location of the argument.
1833+
SourceLoc argLoc = apply->getLoc();
1834+
Type argType;
1835+
if (auto argList = apply->getArgs()) {
1836+
auto arg = argList->get(paramIdx);
1837+
if (arg.getStartLoc().isValid())
1838+
argLoc = arg.getStartLoc();
1839+
1840+
// Determine the type of the argument, ignoring any implicit
1841+
// conversions that could have stripped sendability.
1842+
if (Expr *argExpr = arg.getExpr()) {
1843+
argType = findOriginalValueType(argExpr);
1844+
}
1845+
}
1846+
1847+
if (diagnoseNonSendableTypes(
1848+
argType ? argType : param.getParameterType(),
1849+
declContext, argLoc, diag::non_sendable_call_argument,
1850+
isolationCrossing.value().exitsIsolation(),
1851+
isolationCrossing.value().getDiagnoseIsolation()))
1852+
return true;
1853+
}
1854+
return false;
1855+
}
1856+
17881857
namespace {
17891858
/// Check for adherence to the actor isolation rules, emitting errors
17901859
/// when actor-isolated declarations are used in an unsafe manner.
@@ -2669,33 +2738,6 @@ namespace {
26692738
return result;
26702739
}
26712740

2672-
/// Find the original type of a value, looking through various implicit
2673-
/// conversions.
2674-
static Type findOriginalValueType(Expr *expr) {
2675-
do {
2676-
expr = expr->getSemanticsProvidingExpr();
2677-
2678-
if (auto inout = dyn_cast<InOutExpr>(expr)) {
2679-
expr = inout->getSubExpr();
2680-
continue;
2681-
}
2682-
2683-
if (auto ice = dyn_cast<ImplicitConversionExpr>(expr)) {
2684-
expr = ice->getSubExpr();
2685-
continue;
2686-
}
2687-
2688-
if (auto open = dyn_cast<OpenExistentialExpr>(expr)) {
2689-
expr = open->getSubExpr();
2690-
continue;
2691-
}
2692-
2693-
break;
2694-
} while (true);
2695-
2696-
return expr->getType()->getRValueType();
2697-
}
2698-
26992741
/// Check actor isolation for a particular application.
27002742
bool checkApply(ApplyExpr *apply) {
27012743
auto fnExprType = getType(apply->getFn());
@@ -2819,6 +2861,11 @@ namespace {
28192861
if (!unsatisfiedIsolation)
28202862
return false;
28212863

2864+
// At this point, we know a jump is made to the callee that yields
2865+
// an isolation requirement unsatisfied by the calling context, so
2866+
// set the unsatisfiedIsolationJump fields of the ApplyExpr appropriately
2867+
apply->setIsolationCrossing(getContextIsolation(), *unsatisfiedIsolation);
2868+
28222869
bool requiresAsync =
28232870
callOptions.contains(ActorReferenceResult::Flags::AsyncPromotion);
28242871

@@ -2872,34 +2919,10 @@ namespace {
28722919
unsatisfiedIsolation, setThrows, usesDistributedThunk);
28732920
}
28742921

2875-
// Check for sendability of the parameter types.
2876-
auto params = fnType->getParams();
2877-
for (unsigned paramIdx : indices(params)) {
2878-
const auto &param = params[paramIdx];
2879-
2880-
// Dig out the location of the argument.
2881-
SourceLoc argLoc = apply->getLoc();
2882-
Type argType;
2883-
if (auto argList = apply->getArgs()) {
2884-
auto arg = argList->get(paramIdx);
2885-
if (arg.getStartLoc().isValid())
2886-
argLoc = arg.getStartLoc();
2887-
2888-
// Determine the type of the argument, ignoring any implicit
2889-
// conversions that could have stripped sendability.
2890-
if (Expr *argExpr = arg.getExpr()) {
2891-
argType = findOriginalValueType(argExpr);
2892-
}
2893-
}
2894-
2895-
bool isExiting = !unsatisfiedIsolation->isActorIsolated();
2896-
ActorIsolation diagnoseIsolation = isExiting ? getContextIsolation()
2897-
: *unsatisfiedIsolation;
2898-
if (diagnoseNonSendableTypes(
2899-
argType ? argType : param.getParameterType(), getDeclContext(),
2900-
argLoc, diag::non_sendable_call_argument,
2901-
isExiting, diagnoseIsolation))
2902-
return true;
2922+
// check if language features ask us to defer sendable diagnostics
2923+
// if so, don't check for sendability of arguments here
2924+
if (!ctx.LangOpts.hasFeature(Feature::DeferredSendableChecking)) {
2925+
diagnoseApplyArgSendability(apply, getDeclContext());
29032926
}
29042927

29052928
// Check for sendability of the result type.

lib/Sema/TypeCheckConcurrency.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,11 @@ bool isPotentiallyIsolatedActor(
536536
VarDecl *var, llvm::function_ref<bool(ParamDecl *)> isIsolated =
537537
[](ParamDecl *P) { return P->isIsolated(); });
538538

539+
/// Check whether the given ApplyExpr makes an unsatisfied isolation jump
540+
/// and if so, emit diagnostics for any nonsendable arguments to the apply
541+
bool diagnoseApplyArgSendability(
542+
swift::ApplyExpr *apply, const DeclContext *declContext);
543+
539544
} // end namespace swift
540545

541546
#endif /* SWIFT_SEMA_TYPECHECKCONCURRENCY_H */

0 commit comments

Comments
 (0)