Skip to content

Commit 0371349

Browse files
authored
Merge pull request #75873 from gottesmm/pr-6c64f2f0a43f93b59babd44b8bac2c396a039e2f
[region-isolation] Improve the error we emit for closure literals captured as a sending parameter.
2 parents 0801f07 + 4bb2e4f commit 0371349

12 files changed

+553
-38
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,23 @@ NOTE(regionbasedisolation_typed_tns_passed_to_sending, none,
10251025
"causing races inbetween %0 uses and uses reachable from the callee",
10261026
(StringRef, Type))
10271027

1028+
ERROR(regionbasedisolation_typed_tns_passed_sending_closure, none,
1029+
"passing closure as a 'sending' parameter risks causing data races between %0 and concurrent execution of the closure",
1030+
(StringRef))
1031+
NOTE(regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value, none,
1032+
"closure captures %0 %1",
1033+
(StringRef, DeclName))
1034+
NOTE(regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_task_isolated, none,
1035+
"closure captures %0 which is accessible to code in the current task",
1036+
(DeclName))
1037+
NOTE(regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_region, none,
1038+
"closure captures %1 which is accessible to %0 code",
1039+
(StringRef, DeclName))
1040+
1041+
NOTE(regionbasedisolation_typed_tns_passed_to_sending_closure_helper_multiple_value, none,
1042+
"closure captures non-Sendable %0",
1043+
(DeclName))
1044+
10281045
NOTE(regionbasedisolation_typed_tns_passed_to_sending_callee, none,
10291046
"Passing %0 value of non-Sendable type %1 as a 'sending' parameter to %2 %3 risks causing races inbetween %0 uses and uses reachable from %3",
10301047
(StringRef, Type, DescriptiveDeclKind, DeclName))

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ class RepresentativeValue {
155155
return value.get<SILInstruction *>();
156156
}
157157

158+
bool operator==(SILValue other) const {
159+
if (hasRegionIntroducingInst())
160+
return false;
161+
return getValue() == other;
162+
}
163+
158164
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
159165

160166
private:

lib/SILOptimizer/Mandatory/TransferNonSendable.cpp

Lines changed: 242 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,29 @@ static llvm::cl::opt<bool> ForceTypedErrors(
6969
// MARK: Utilities
7070
//===----------------------------------------------------------------------===//
7171

72+
static SILValue stripFunctionConversions(SILValue val) {
73+
while (true) {
74+
if (auto ti = dyn_cast<ThinToThickFunctionInst>(val)) {
75+
val = ti->getOperand();
76+
continue;
77+
}
78+
79+
if (auto cfi = dyn_cast<ConvertFunctionInst>(val)) {
80+
val = cfi->getOperand();
81+
continue;
82+
}
83+
84+
if (auto cvt = dyn_cast<ConvertEscapeToNoEscapeInst>(val)) {
85+
val = cvt->getOperand();
86+
continue;
87+
}
88+
89+
break;
90+
}
91+
92+
return val;
93+
}
94+
7295
static std::optional<DiagnosticBehavior>
7396
getDiagnosticBehaviorLimitForValue(SILValue value) {
7497
auto *nom = value->getType().getNominalOrBoundGenericNominal();
@@ -83,6 +106,38 @@ getDiagnosticBehaviorLimitForValue(SILValue value) {
83106
return getConcurrencyDiagnosticBehaviorLimit(nom, fromDC);
84107
}
85108

109+
static std::optional<DiagnosticBehavior>
110+
getDiagnosticBehaviorLimitForCapturedValue(CapturedValue value) {
111+
ValueDecl *decl = value.getDecl();
112+
auto *nom = decl->getInterfaceType()->getNominalOrBoundGenericNominal();
113+
if (!nom)
114+
return {};
115+
116+
auto *fromDC = decl->getInnermostDeclContext();
117+
return getConcurrencyDiagnosticBehaviorLimit(nom, fromDC);
118+
}
119+
120+
/// Find the most conservative diagnostic behavior by taking the max over all
121+
/// DiagnosticBehavior for the captured values.
122+
static std::optional<DiagnosticBehavior>
123+
getDiagnosticBehaviorLimitForCapturedValues(
124+
ArrayRef<CapturedValue> capturedValues) {
125+
using UnderlyingType = std::underlying_type<DiagnosticBehavior>::type;
126+
127+
std::optional<DiagnosticBehavior> diagnosticBehavior;
128+
for (auto value : capturedValues) {
129+
auto lhs = UnderlyingType(
130+
diagnosticBehavior.value_or(DiagnosticBehavior::Unspecified));
131+
auto rhs = UnderlyingType(
132+
getDiagnosticBehaviorLimitForCapturedValue(value).value_or(
133+
DiagnosticBehavior::Unspecified));
134+
auto result = DiagnosticBehavior(std::max(lhs, rhs));
135+
if (result != DiagnosticBehavior::Unspecified)
136+
diagnosticBehavior = result;
137+
}
138+
return diagnosticBehavior;
139+
}
140+
86141
static std::optional<SILDeclRef> getDeclRefForCallee(SILInstruction *inst) {
87142
auto fas = FullApplySite::isa(inst);
88143
if (!fas)
@@ -1390,6 +1445,98 @@ class TransferNonTransferrableDiagnosticEmitter {
13901445
}
13911446
}
13921447

1448+
/// Only use if we were able to find the actual isolated value.
1449+
void emitTypedSendingNeverSendableToSendingClosureParamDirectlyIsolated(
1450+
SILLocation loc, CapturedValue capturedValue) {
1451+
SmallString<64> descriptiveKindStr;
1452+
{
1453+
llvm::raw_svector_ostream os(descriptiveKindStr);
1454+
if (getIsolationRegionInfo().getIsolationInfo().isTaskIsolated()) {
1455+
os << "code in the current task";
1456+
} else {
1457+
getIsolationRegionInfo().printForDiagnostics(os);
1458+
os << " code";
1459+
}
1460+
}
1461+
1462+
diagnoseError(loc,
1463+
diag::regionbasedisolation_typed_tns_passed_sending_closure,
1464+
descriptiveKindStr)
1465+
.highlight(loc.getSourceRange())
1466+
.limitBehaviorIf(
1467+
getDiagnosticBehaviorLimitForCapturedValue(capturedValue));
1468+
1469+
auto capturedLoc = RegularLocation(capturedValue.getLoc());
1470+
if (getIsolationRegionInfo().getIsolationInfo().isTaskIsolated()) {
1471+
auto diag = diag::
1472+
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_task_isolated;
1473+
diagnoseNote(capturedLoc, diag, capturedValue.getDecl()->getName());
1474+
return;
1475+
}
1476+
1477+
descriptiveKindStr.clear();
1478+
{
1479+
llvm::raw_svector_ostream os(descriptiveKindStr);
1480+
getIsolationRegionInfo().printForDiagnostics(os);
1481+
}
1482+
1483+
auto diag = diag::
1484+
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value;
1485+
diagnoseNote(capturedLoc, diag, descriptiveKindStr,
1486+
capturedValue.getDecl()->getName());
1487+
}
1488+
1489+
void emitTypedSendingNeverSendableToSendingClosureParam(
1490+
SILLocation loc, ArrayRef<CapturedValue> capturedValues) {
1491+
SmallString<64> descriptiveKindStr;
1492+
{
1493+
llvm::raw_svector_ostream os(descriptiveKindStr);
1494+
if (getIsolationRegionInfo().getIsolationInfo().isTaskIsolated()) {
1495+
os << "code in the current task";
1496+
} else {
1497+
getIsolationRegionInfo().printForDiagnostics(os);
1498+
os << " code";
1499+
}
1500+
}
1501+
1502+
auto behaviorLimit =
1503+
getDiagnosticBehaviorLimitForCapturedValues(capturedValues);
1504+
diagnoseError(loc,
1505+
diag::regionbasedisolation_typed_tns_passed_sending_closure,
1506+
descriptiveKindStr)
1507+
.highlight(loc.getSourceRange())
1508+
.limitBehaviorIf(behaviorLimit);
1509+
1510+
if (capturedValues.size() == 1) {
1511+
auto captured = capturedValues.front();
1512+
auto capturedLoc = RegularLocation(captured.getLoc());
1513+
if (getIsolationRegionInfo().getIsolationInfo().isTaskIsolated()) {
1514+
auto diag = diag::
1515+
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_task_isolated;
1516+
diagnoseNote(capturedLoc, diag, captured.getDecl()->getName());
1517+
return;
1518+
}
1519+
1520+
descriptiveKindStr.clear();
1521+
{
1522+
llvm::raw_svector_ostream os(descriptiveKindStr);
1523+
getIsolationRegionInfo().printForDiagnostics(os);
1524+
}
1525+
auto diag = diag::
1526+
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_have_value_region;
1527+
diagnoseNote(capturedLoc, diag, descriptiveKindStr,
1528+
captured.getDecl()->getName());
1529+
return;
1530+
}
1531+
1532+
for (auto captured : capturedValues) {
1533+
auto capturedLoc = RegularLocation(captured.getLoc());
1534+
auto diag = diag::
1535+
regionbasedisolation_typed_tns_passed_to_sending_closure_helper_multiple_value;
1536+
diagnoseNote(capturedLoc, diag, captured.getDecl()->getName());
1537+
}
1538+
}
1539+
13931540
void emitNamedOnlyError(SILLocation loc, Identifier name) {
13941541
diagnoseError(loc, diag::regionbasedisolation_named_transfer_yields_race,
13951542
name)
@@ -1527,12 +1674,13 @@ class TransferNonTransferrableDiagnosticEmitter {
15271674
class TransferNonTransferrableDiagnosticInferrer {
15281675
struct AutoClosureWalker;
15291676

1677+
RegionAnalysisValueMap &valueMap;
15301678
TransferNonTransferrableDiagnosticEmitter diagnosticEmitter;
15311679

15321680
public:
15331681
TransferNonTransferrableDiagnosticInferrer(
1534-
TransferredNonTransferrableInfo info)
1535-
: diagnosticEmitter(info) {}
1682+
RegionAnalysisValueMap &valueMap, TransferredNonTransferrableInfo info)
1683+
: valueMap(valueMap), diagnosticEmitter(info) {}
15361684

15371685
/// Gathers diagnostics. Returns false if we emitted a "I don't understand
15381686
/// error". If we emit such an error, we should bail without emitting any
@@ -1546,10 +1694,94 @@ class TransferNonTransferrableDiagnosticInferrer {
15461694
bool initForIsolatedPartialApply(
15471695
Operand *op, AbstractClosureExpr *ace,
15481696
std::optional<ActorIsolation> actualCallerIsolation = {});
1697+
1698+
bool initForSendingPartialApply(FullApplySite fas, Operand *pai);
1699+
1700+
std::optional<unsigned>
1701+
getIsolatedValuePartialApplyIndex(PartialApplyInst *pai,
1702+
SILValue isolatedValue) {
1703+
for (auto &paiOp : ApplySite(pai).getArgumentOperands()) {
1704+
if (valueMap.getTrackableValue(paiOp.get()).getRepresentative() ==
1705+
isolatedValue) {
1706+
// isolated_any causes all partial apply parameters to be shifted by 1
1707+
// due to the implicit isolated any parameter.
1708+
unsigned isIsolatedAny = pai->getFunctionType()->getIsolation() ==
1709+
SILFunctionTypeIsolation::Erased;
1710+
return ApplySite(pai).getAppliedArgIndex(paiOp) - isIsolatedAny;
1711+
}
1712+
}
1713+
1714+
return {};
1715+
}
15491716
};
15501717

15511718
} // namespace
15521719

1720+
bool TransferNonTransferrableDiagnosticInferrer::initForSendingPartialApply(
1721+
FullApplySite fas, Operand *paiOp) {
1722+
auto *pai =
1723+
dyn_cast<PartialApplyInst>(stripFunctionConversions(paiOp->get()));
1724+
if (!pai)
1725+
return false;
1726+
1727+
// For now we want this to be really narrow and to only apply to closure
1728+
// literals.
1729+
auto *ce = pai->getLoc().getAsASTNode<ClosureExpr>();
1730+
if (!ce)
1731+
return false;
1732+
1733+
// Ok, we now know we have a partial apply and it is a closure literal. Lets
1734+
// see if we can find the exact thing that caused the closure literal to be
1735+
// actor isolated.
1736+
auto isolationInfo = diagnosticEmitter.getIsolationRegionInfo();
1737+
if (isolationInfo->hasIsolatedValue()) {
1738+
// Now that we have the value, see if that value is one of our captured
1739+
// values.
1740+
auto isolatedValue = isolationInfo->getIsolatedValue();
1741+
auto matchingElt = getIsolatedValuePartialApplyIndex(pai, isolatedValue);
1742+
if (matchingElt) {
1743+
// Ok, we found the matching element. Lets emit our diagnostic!
1744+
auto capture = ce->getCaptureInfo().getCaptures()[*matchingElt];
1745+
diagnosticEmitter
1746+
.emitTypedSendingNeverSendableToSendingClosureParamDirectlyIsolated(
1747+
ce, capture);
1748+
return true;
1749+
}
1750+
}
1751+
1752+
// Ok, we are not tracking an actual isolated value or we do not capture the
1753+
// isolated value directly... we need to be smarter here. First lets gather up
1754+
// all non-Sendable values captured by the closure.
1755+
SmallVector<CapturedValue, 8> nonSendableCaptures;
1756+
for (auto capture : ce->getCaptureInfo().getCaptures()) {
1757+
auto *decl = capture.getDecl();
1758+
auto type = decl->getInterfaceType()->getCanonicalType();
1759+
auto silType = SILType::getPrimitiveObjectType(type);
1760+
if (!SILIsolationInfo::isNonSendableType(silType, pai->getFunction()))
1761+
continue;
1762+
1763+
auto *fromDC = decl->getInnermostDeclContext();
1764+
auto *nom = silType.getNominalOrBoundGenericNominal();
1765+
if (nom && fromDC) {
1766+
if (auto diagnosticBehavior =
1767+
getConcurrencyDiagnosticBehaviorLimit(nom, fromDC)) {
1768+
if (*diagnosticBehavior == DiagnosticBehavior::Ignore)
1769+
continue;
1770+
}
1771+
}
1772+
nonSendableCaptures.push_back(capture);
1773+
}
1774+
1775+
// If we do not have any non-Sendable captures... bail.
1776+
if (nonSendableCaptures.empty())
1777+
return false;
1778+
1779+
// Otherwise, emit the diagnostic.
1780+
diagnosticEmitter.emitTypedSendingNeverSendableToSendingClosureParam(
1781+
ce, nonSendableCaptures);
1782+
return true;
1783+
}
1784+
15531785
bool TransferNonTransferrableDiagnosticInferrer::initForIsolatedPartialApply(
15541786
Operand *op, AbstractClosureExpr *ace,
15551787
std::optional<ActorIsolation> actualCallerIsolation) {
@@ -1657,6 +1889,11 @@ bool TransferNonTransferrableDiagnosticInferrer::run() {
16571889
if (auto fas = FullApplySite::isa(op->getUser())) {
16581890
if (fas.getArgumentParameterInfo(*op).hasOption(
16591891
SILParameterInfo::Sending)) {
1892+
// Before we do anything, lets see if we are passing a sendable closure
1893+
// literal. If we do, we want to emit a special error that states which
1894+
// captured value caused the actual error.
1895+
if (initForSendingPartialApply(fas, op))
1896+
return true;
16601897

16611898
// See if we can infer a name from the value.
16621899
SmallString<64> resultingName;
@@ -1671,6 +1908,7 @@ bool TransferNonTransferrableDiagnosticInferrer::run() {
16711908
inferArgumentExprFromApplyExpr(sourceApply, fas, op)) {
16721909
type = inferredArgExpr->findOriginalType();
16731910
}
1911+
16741912
diagnosticEmitter.emitTypedSendingNeverSendableToSendingParam(loc,
16751913
type);
16761914
return true;
@@ -1796,7 +2034,8 @@ void TransferNonSendableImpl::emitTransferredNonTransferrableDiagnostics() {
17962034
llvm::dbgs() << "Emitting transfer non transferrable diagnostics.\n");
17972035

17982036
for (auto info : transferredNonTransferrableInfoList) {
1799-
TransferNonTransferrableDiagnosticInferrer diagnosticInferrer(info);
2037+
TransferNonTransferrableDiagnosticInferrer diagnosticInferrer(
2038+
regionInfo->getValueMap(), info);
18002039
diagnosticInferrer.run();
18012040
}
18022041
}

test/Concurrency/concurrent_value_checking.swift

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -413,24 +413,20 @@ extension NotConcurrent {
413413
func f() { }
414414

415415
func test() {
416-
Task { // expected-tns-warning {{sending value of non-Sendable type '() async -> ()' risks causing data races}}
417-
// expected-tns-note @-1 {{Passing task-isolated value of non-Sendable type '() async -> ()' as a 'sending' parameter risks causing races inbetween task-isolated uses and uses reachable from the callee}}
418-
f()
416+
Task { // expected-tns-warning {{passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure}}
417+
f() // expected-tns-note {{closure captures 'self' which is accessible to code in the current task}}
419418
}
420419

421-
Task { // expected-tns-warning {{sending value of non-Sendable type '() async -> ()' risks causing data races}}
422-
// expected-tns-note @-1 {{Passing task-isolated value of non-Sendable type '() async -> ()' as a 'sending' parameter risks causing races inbetween task-isolated uses and uses reachable from the callee}}
423-
self.f()
420+
Task { // expected-tns-warning {{passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure}}
421+
self.f() // expected-tns-note {{closure captures 'self' which is accessible to code in the current task}}
424422
}
425423

426-
Task { [self] in // expected-tns-warning {{sending value of non-Sendable type '() async -> ()' risks causing data races}}
427-
// expected-tns-note @-1 {{Passing task-isolated value of non-Sendable type '() async -> ()' as a 'sending' parameter risks causing races inbetween task-isolated uses and uses reachable from the callee}}
428-
f()
424+
Task { [self] in // expected-tns-warning {{passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure}}
425+
f() // expected-tns-note {{closure captures 'self' which is accessible to code in the current task}}
429426
}
430427

431-
Task { [self] in // expected-tns-warning {{sending value of non-Sendable type '() async -> ()' risks causing data races}}
432-
// expected-tns-note @-1 {{Passing task-isolated value of non-Sendable type '() async -> ()' as a 'sending' parameter risks causing races inbetween task-isolated uses and uses reachable from the callee}}
433-
self.f()
428+
Task { [self] in // expected-tns-warning {{passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure}}
429+
self.f() // expected-tns-note {{closure captures 'self' which is accessible to code in the current task}}
434430
}
435431
}
436432
}

test/Concurrency/isolated_parameters.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -501,10 +501,9 @@ func testNonSendableCaptures(ns: NotSendable, a: isolated MyActor) {
501501

502502
// FIXME: The `a` in the capture list and `isolated a` are the same,
503503
// but the actor isolation checker doesn't know that.
504-
Task { [a] in // expected-tns-warning {{sending value of non-Sendable type '() async -> ()' risks causing data races}}
505-
// expected-tns-note @-1 {{Passing 'a'-isolated value of non-Sendable type '() async -> ()' as a 'sending' parameter risks causing races inbetween 'a'-isolated uses and uses reachable from the callee}}
504+
Task { [a] in // expected-tns-warning {{passing closure as a 'sending' parameter risks causing data races between 'a'-isolated code and concurrent execution of the closure}}
506505
_ = a
507-
_ = ns
506+
_ = ns // expected-tns-note {{closure captures 'a'-isolated 'ns'}}
508507
}
509508
}
510509

0 commit comments

Comments
 (0)