Skip to content

Commit f077e4a

Browse files
committed
[region-isolation] Fix the call site or self error for values used in the same region as a function argument.
This is just good to do and also makes it so that in my test case for assumeIsolated, I get a better msg.
1 parent 8ff9341 commit f077e4a

9 files changed

+288
-37
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,8 +907,14 @@ WARNING(regionbasedisolation_transfer_yields_race_transferring_parameter, none,
907907
WARNING(regionbasedisolation_transfer_yields_race_stronglytransferred_binding, none,
908908
"binding of non-Sendable type %0 accessed after being transferred; later accesses could race",
909909
(Type))
910+
WARNING(regionbasedisolation_arg_transferred, none,
911+
"task isolated value of type %0 transferred to %1 context; later accesses to value could race",
912+
(Type, ActorIsolation))
910913
NOTE(regionbasedisolation_maybe_race, none,
911914
"access here could race", ())
915+
NOTE(regionbasedisolation_isolated_since_in_same_region_basename, none,
916+
"value is %0 since it is in the same region as %1",
917+
(StringRef, DeclBaseName))
912918
ERROR(regionbasedisolation_unknown_pattern, none,
913919
"pattern that the region based isolation checker does not understand how to check. Please file a bug",
914920
())

lib/SILOptimizer/Analysis/RegionAnalysis.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,12 +1604,30 @@ class PartitionOpTranslator {
16041604

16051605
// First check if our partial_apply is fed into an async let begin. If so,
16061606
// handle it especially.
1607+
//
1608+
// NOTE: If it is an async_let, then the closure itself will be Sendable. We
1609+
// treat passing in a value into the async Sendable closure as transferring
1610+
// it into the closure.
16071611
if (isAsyncLetBeginPartialApply(pai)) {
16081612
return translateSILPartialApplyAsyncLetBegin(pai);
16091613
}
16101614

1611-
// Check if our closure is isolated to a specific actor. If it is, we need
1612-
// to treat all of the captures as being transferred.
1615+
// Then check if our partial apply is Sendable. In such a case, we will have
1616+
// emitted an earlier warning in Sema.
1617+
//
1618+
// DISCUSSION: The reason why we can treat values passed into an async let
1619+
// as transferring safely but it is unsafe to do this for arbitrary Sendable
1620+
// closures is that we do not know how many times the Sendable closure will
1621+
// be executed. It is possible to have different invocations of the Sendable
1622+
// closure to cause races with the captured non-Sendable value. In contrast
1623+
// since we know an async let runs exactly once, we do not need to worry
1624+
// about such a possibility. If we had the ability in the language to
1625+
// specify that a closure is run at most once or that it is always run
1626+
// serially, we could lift this restriction... so for now we leave in the
1627+
// Sema warning and just bail here.
1628+
if (pai->getFunctionType()->isSendableType())
1629+
return;
1630+
16131631
if (auto *ace = pai->getLoc().getAsASTNode<AbstractClosureExpr>()) {
16141632
if (ace->getActorIsolation().isActorIsolated()) {
16151633
return translateIsolatedPartialApply(pai);

lib/SILOptimizer/Mandatory/TransferNonSendable.cpp

Lines changed: 204 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -600,19 +600,18 @@ void UseAfterTransferDiagnosticInferrer::init(const Operand *op) {
600600
}
601601
}
602602

603+
auto loc = op->getUser()->getLoc();
604+
603605
// If we have a partial_apply that is actor isolated, see if we found a
604606
// transfer error due to us transferring a value into it.
605-
if (auto pai = dyn_cast<PartialApplyInst>(nonConstOp->getUser())) {
606-
if (auto *ace = pai->getLoc().getAsASTNode<AbstractClosureExpr>()) {
607-
if (ace->getActorIsolation().isActorIsolated()) {
608-
if (initForIsolatedPartialApply(nonConstOp, ace)) {
609-
return;
610-
}
607+
if (auto *ace = loc.getAsASTNode<AbstractClosureExpr>()) {
608+
if (ace->getActorIsolation().isActorIsolated()) {
609+
if (initForIsolatedPartialApply(nonConstOp, ace)) {
610+
return;
611611
}
612612
}
613613
}
614614

615-
auto loc = op->getUser()->getLoc();
616615
if (auto *sourceApply = loc.getAsASTNode<ApplyExpr>()) {
617616
return initForApply(op, sourceApply);
618617
}
@@ -639,6 +638,132 @@ void UseAfterTransferDiagnosticInferrer::init(const Operand *op) {
639638
autoClosureExpr->walk(walker);
640639
}
641640

641+
//===----------------------------------------------------------------------===//
642+
// MARK: Transfer NonTransferrable Diagnostic Inference
643+
//===----------------------------------------------------------------------===//
644+
645+
namespace {
646+
647+
struct TransferredNonTransferrableInfo {
648+
/// The use that actually caused the transfer.
649+
Operand *transferredOperand;
650+
651+
/// The non-transferrable value that is in the same region as \p
652+
/// transferredOperand.get().
653+
SILValue nonTransferrableValue;
654+
655+
TransferredNonTransferrableInfo(Operand *transferredOperand,
656+
SILValue nonTransferrableValue)
657+
: transferredOperand(transferredOperand),
658+
nonTransferrableValue(nonTransferrableValue) {}
659+
};
660+
661+
class TransferNonTransferrableDiagnosticInferrer {
662+
public:
663+
enum class UseDiagnosticInfoKind {
664+
Invalid = 0,
665+
666+
/// Used if we have a use that we haven't categorized so we emit the generic
667+
/// call site is self error.
668+
MiscUse = 1,
669+
670+
/// Used if we have a function argument that is transferred into an apply.
671+
FunctionArgumentApply = 2,
672+
673+
/// Used if we have a function argument that is transferred into an closure.
674+
FunctionArgumentClosure = 3,
675+
};
676+
677+
struct UseDiagnosticInfo {
678+
UseDiagnosticInfoKind kind;
679+
std::optional<ActorIsolation> transferredIsolation;
680+
681+
static UseDiagnosticInfo forMiscUse() {
682+
return {UseDiagnosticInfoKind::MiscUse, {}};
683+
}
684+
685+
static UseDiagnosticInfo
686+
forFunctionArgumentApply(ActorIsolation isolation) {
687+
return {UseDiagnosticInfoKind::FunctionArgumentApply, isolation};
688+
}
689+
690+
static UseDiagnosticInfo
691+
forFunctionArgumentClosure(ActorIsolation isolation) {
692+
return {UseDiagnosticInfoKind::FunctionArgumentClosure, isolation};
693+
}
694+
695+
private:
696+
UseDiagnosticInfo(UseDiagnosticInfoKind kind,
697+
std::optional<ActorIsolation> isolation)
698+
: kind(kind), transferredIsolation(isolation) {}
699+
};
700+
701+
private:
702+
TransferredNonTransferrableInfo info;
703+
std::optional<UseDiagnosticInfo> diagnosticInfo;
704+
SourceLoc loc;
705+
706+
public:
707+
TransferNonTransferrableDiagnosticInferrer(
708+
TransferredNonTransferrableInfo info)
709+
: info(info),
710+
loc(info.transferredOperand->getUser()->getLoc().getSourceLoc()) {}
711+
712+
void run();
713+
714+
UseDiagnosticInfo getDiagnostic() const { return diagnosticInfo.value(); }
715+
SourceLoc getLoc() const { return loc; }
716+
717+
private:
718+
bool initForIsolatedPartialApply(Operand *op, AbstractClosureExpr *ace);
719+
};
720+
721+
} // namespace
722+
723+
bool TransferNonTransferrableDiagnosticInferrer::initForIsolatedPartialApply(
724+
Operand *op, AbstractClosureExpr *ace) {
725+
SmallVector<std::tuple<CapturedValue, unsigned, ApplyIsolationCrossing>, 8>
726+
foundCapturedIsolationCrossing;
727+
ace->getIsolationCrossing(foundCapturedIsolationCrossing);
728+
if (foundCapturedIsolationCrossing.empty())
729+
return false;
730+
731+
unsigned opIndex = ApplySite(op->getUser()).getAppliedArgIndex(*op);
732+
for (auto &p : foundCapturedIsolationCrossing) {
733+
if (std::get<1>(p) == opIndex) {
734+
loc = std::get<0>(p).getLoc();
735+
diagnosticInfo = UseDiagnosticInfo::forFunctionArgumentClosure(
736+
std::get<2>(p).getCalleeIsolation());
737+
return true;
738+
}
739+
}
740+
741+
return false;
742+
}
743+
744+
void TransferNonTransferrableDiagnosticInferrer::run() {
745+
if (isa<SILFunctionArgument>(info.nonTransferrableValue)) {
746+
// We need to find the isolation info.
747+
auto loc = info.transferredOperand->getUser()->getLoc();
748+
749+
if (auto *sourceApply = loc.getAsASTNode<ApplyExpr>()) {
750+
diagnosticInfo = UseDiagnosticInfo::forFunctionArgumentApply(
751+
sourceApply->getIsolationCrossing().value().getCalleeIsolation());
752+
return;
753+
}
754+
755+
if (auto *ace = loc.getAsASTNode<AbstractClosureExpr>()) {
756+
if (ace->getActorIsolation().isActorIsolated()) {
757+
if (initForIsolatedPartialApply(info.transferredOperand, ace)) {
758+
return;
759+
}
760+
}
761+
}
762+
}
763+
764+
diagnosticInfo = UseDiagnosticInfo::forMiscUse();
765+
}
766+
642767
//===----------------------------------------------------------------------===//
643768
// MARK: Diagnostic Emission
644769
//===----------------------------------------------------------------------===//
@@ -650,13 +775,18 @@ struct DiagnosticEvaluator final
650775
RegionAnalysisFunctionInfo *info;
651776
SmallFrozenMultiMap<Operand *, SILInstruction *, 8>
652777
&transferOpToRequireInstMultiMap;
653-
SmallVectorImpl<Operand *> &transferredNonTransferrable;
778+
779+
/// First value is the operand that was transferred... second value is the
780+
/// non-transferrable value in the same region as that value. The second value
781+
/// is what is non-transferrable.
782+
SmallVectorImpl<TransferredNonTransferrableInfo> &transferredNonTransferrable;
654783

655784
DiagnosticEvaluator(Partition &workingPartition,
656785
RegionAnalysisFunctionInfo *info,
657786
SmallFrozenMultiMap<Operand *, SILInstruction *, 8>
658787
&transferOpToRequireInstMultiMap,
659-
SmallVectorImpl<Operand *> &transferredNonTransferrable)
788+
SmallVectorImpl<TransferredNonTransferrableInfo>
789+
&transferredNonTransferrable)
660790
: PartitionOpEvaluatorBaseImpl(workingPartition,
661791
info->getOperandSetFactory()),
662792
info(info),
@@ -706,7 +836,10 @@ struct DiagnosticEvaluator final
706836
<< " Rep: "
707837
<< *info->getValueMap().getRepresentative(transferredVal));
708838
auto *self = const_cast<DiagnosticEvaluator *>(this);
709-
self->transferredNonTransferrable.push_back(partitionOp.getSourceOp());
839+
auto nonTransferrableValue =
840+
info->getValueMap().getRepresentative(transferredVal);
841+
self->transferredNonTransferrable.emplace_back(partitionOp.getSourceOp(),
842+
nonTransferrableValue);
710843
}
711844

712845
bool isActorDerived(Element element) const {
@@ -729,7 +862,7 @@ class TransferNonSendableImpl {
729862
RegionAnalysisFunctionInfo *regionInfo;
730863
SmallFrozenMultiMap<Operand *, SILInstruction *, 8>
731864
transferOpToRequireInstMultiMap;
732-
SmallVector<Operand *, 8> transferredNonTransferrable;
865+
SmallVector<TransferredNonTransferrableInfo, 8> transferredNonTransferrable;
733866

734867
public:
735868
TransferNonSendableImpl(RegionAnalysisFunctionInfo *regionInfo)
@@ -889,8 +1022,66 @@ void TransferNonSendableImpl::emitUseAfterTransferDiagnostics() {
8891022
}
8901023

8911024
void TransferNonSendableImpl::emitTransferredNonTransferrableDiagnostics() {
892-
for (auto *op : transferredNonTransferrable) {
893-
diagnose(op->getUser(), diag::regionbasedisolation_selforargtransferred);
1025+
if (transferredNonTransferrable.empty())
1026+
return;
1027+
1028+
using UseDiagnosticInfoKind =
1029+
TransferNonTransferrableDiagnosticInferrer::UseDiagnosticInfoKind;
1030+
1031+
auto &astContext = regionInfo->getFunction()->getASTContext();
1032+
for (auto info : transferredNonTransferrable) {
1033+
auto *op = info.transferredOperand;
1034+
TransferNonTransferrableDiagnosticInferrer diagnosticInferrer(info);
1035+
diagnosticInferrer.run();
1036+
1037+
auto diagnosticInfo = diagnosticInferrer.getDiagnostic();
1038+
auto loc = diagnosticInferrer.getLoc();
1039+
1040+
switch (diagnosticInfo.kind) {
1041+
case UseDiagnosticInfoKind::Invalid:
1042+
llvm_unreachable("Should never see this");
1043+
case UseDiagnosticInfoKind::MiscUse:
1044+
diagnose(astContext, loc,
1045+
diag::regionbasedisolation_selforargtransferred);
1046+
break;
1047+
case UseDiagnosticInfoKind::FunctionArgumentApply: {
1048+
diagnose(astContext, loc, diag::regionbasedisolation_arg_transferred,
1049+
op->get()->getType().getASTType(),
1050+
diagnosticInfo.transferredIsolation.value())
1051+
.highlight(op->getUser()->getLoc().getSourceRange());
1052+
// Only emit the note if our value is different from the function
1053+
// argument.
1054+
auto rep = regionInfo->getValueMap()
1055+
.getTrackableValue(op->get())
1056+
.getRepresentative();
1057+
if (rep.maybeGetValue() == info.nonTransferrableValue)
1058+
continue;
1059+
auto *fArg = cast<SILFunctionArgument>(info.nonTransferrableValue);
1060+
diagnose(
1061+
astContext, fArg->getDecl()->getLoc(),
1062+
diag::regionbasedisolation_isolated_since_in_same_region_basename,
1063+
"task isolated", fArg->getDecl()->getBaseName());
1064+
break;
1065+
}
1066+
case UseDiagnosticInfoKind::FunctionArgumentClosure: {
1067+
diagnose(astContext, loc, diag::regionbasedisolation_arg_transferred,
1068+
op->get()->getType().getASTType(),
1069+
diagnosticInfo.transferredIsolation.value())
1070+
.highlight(op->getUser()->getLoc().getSourceRange());
1071+
// Only emit the note if our value is different from the function
1072+
// argument.
1073+
auto rep = regionInfo->getValueMap()
1074+
.getTrackableValue(op->get())
1075+
.getRepresentative();
1076+
if (rep.maybeGetValue() == info.nonTransferrableValue)
1077+
continue;
1078+
auto *fArg = cast<SILFunctionArgument>(info.nonTransferrableValue);
1079+
diagnose(
1080+
astContext, fArg->getDecl()->getLoc(),
1081+
diag::regionbasedisolation_isolated_since_in_same_region_basename,
1082+
"task isolated", fArg->getDecl()->getBaseName());
1083+
}
1084+
}
8941085
}
8951086
}
8961087

test/Concurrency/sendable_checking.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,11 @@ final class NonSendable {
251251
func call() async {
252252
await update()
253253
// expected-targeted-and-complete-warning @-1 {{passing argument of non-sendable type 'NonSendable' into main actor-isolated context may introduce data races}}
254-
// expected-tns-warning @-2 {{call site passes `self` or a non-sendable argument of this function to another thread, potentially yielding a race with the caller}}
254+
// expected-tns-warning @-2 {{task isolated value of type 'NonSendable' transferred to main actor-isolated context; later accesses to value could race}}
255255

256256
await self.update()
257257
// expected-targeted-and-complete-warning @-1 {{passing argument of non-sendable type 'NonSendable' into main actor-isolated context may introduce data races}}
258-
// expected-tns-warning @-2 {{call site passes `self` or a non-sendable argument of this function to another thread, potentially yielding a race with the caller}}
258+
// expected-tns-warning @-2 {{task isolated value of type 'NonSendable' transferred to main actor-isolated context; later accesses to value could race}}
259259

260260
_ = await x
261261
// expected-warning@-1 {{non-sendable type 'NonSendable' passed in implicitly asynchronous call to main actor-isolated property 'x' cannot cross actor boundary}}
@@ -287,18 +287,18 @@ func globalSendable(_ ns: NonSendable) async {}
287287
@available(SwiftStdlib 5.1, *)
288288
@MainActor
289289
func callNonisolatedAsyncClosure(
290-
ns: NonSendable,
290+
ns: NonSendable, // expected-tns-note {{value is task isolated since it is in the same region as 'ns'}}
291291
g: (NonSendable) async -> Void
292292
) async {
293293
await g(ns)
294294
// expected-targeted-and-complete-warning @-1 {{passing argument of non-sendable type 'NonSendable' outside of main actor-isolated context may introduce data races}}
295-
// expected-tns-warning @-2 {{call site passes `self` or a non-sendable argument of this function to another thread, potentially yielding a race with the caller}}
296-
// expected-tns-warning @-3 {{call site passes `self` or a non-sendable argument of this function to another thread, potentially yielding a race with the caller}}
295+
// expected-tns-warning @-2 {{task isolated value of type 'NonSendable' transferred to nonisolated context; later accesses to value could race}}
296+
// expected-tns-warning @-3 {{task isolated value of type '@noescape @async @callee_guaranteed (@guaranteed NonSendable) -> ()' transferred to nonisolated context; later accesses to value could race}}
297297

298298
let f: (NonSendable) async -> () = globalSendable // okay
299299
await f(ns)
300300
// expected-targeted-and-complete-warning@-1 {{passing argument of non-sendable type 'NonSendable' outside of main actor-isolated context may introduce data races}}
301-
// expected-tns-warning @-2 {{call site passes `self` or a non-sendable argument of this function to another thread, potentially yielding a race with the caller}}
301+
// expected-tns-warning @-2 {{task isolated value of type 'NonSendable' transferred to nonisolated context; later accesses to value could race}}
302302

303303
}
304304

0 commit comments

Comments
 (0)