Skip to content

Commit a83d7aa

Browse files
author
jturcotti
committed
improve diagnostics about data races; highlight the individual expressions being sent and accessed as precisely as possible, and include information about specific non-sendable types and isolation crossings
1 parent 992fc6c commit a83d7aa

File tree

5 files changed

+131
-51
lines changed

5 files changed

+131
-51
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -864,14 +864,16 @@ NOTE(sil_referencebinding_inout_binding_here, none,
864864

865865
// Warnings arising from the flow-sensitive checking of Sendability of
866866
// non-Sendable values
867-
WARNING(consumed_value_used, none,
868-
"non-Sendable value consumed, then used at this site; could yield race with another thread", ())
869867
WARNING(arg_region_consumed, none,
870868
"this application could pass `self` or a Non-Sendable argument of this function to another thread, potentially yielding a race with the caller", ())
871869
WARNING(consumption_yields_race, none,
872-
"non-Sendable value sent across isolation domains here, but could be accessed later in this function (%0 access site%select{|s}1 displayed%select{|, %3 more hidden}2)", (unsigned, bool, bool, unsigned))
870+
"non-sendable value sent across isolation domains here, but could be accessed later in this function (%0 access site%select{|s}1 displayed%select{|, %3 more hidden}2)",
871+
(unsigned, bool, bool, unsigned))
872+
WARNING(call_site_consumption_yields_race, none,
873+
"passing argument of non-sendable type %0 from %1 context to %2 context at this call site could yield a race with accesses later in this function (%3 access site%select{|s}4 displayed%select{|, %6 more hidden}5)",
874+
(Type, ActorIsolation, ActorIsolation, unsigned, bool, bool, unsigned))
873875
NOTE(possible_racy_access_site, none,
874-
"access here could race with non-Sendable value send above", ())
876+
"access here could race", ())
875877

876878
#define UNDEFINE_DIAGNOSTIC_MACROS
877879
#include "DefineDiagnosticMacros.h"

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ class PartitionOp {
145145
return sourceInst;
146146
}
147147

148+
Expr *getSourceExpr() const {
149+
return sourceExpr;
150+
}
151+
148152
void dump() const LLVM_ATTRIBUTE_USED {
149153
raw_ostream &os = llvm::errs();
150154
switch (OpKind) {

lib/SILOptimizer/Mandatory/SendNonSendable.cpp

Lines changed: 116 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,18 @@ namespace {
2424
static const char *SEP_STR = "╾──────────────────────────────╼\n";
2525

2626
// SILApplyCrossesIsolation determines if a SIL instruction is an isolation
27-
// crossing apply expressiong. This is done by checking its correspondence
27+
// crossing apply expression. This is done by checking its correspondence
2828
// to an ApplyExpr AST node, and then checking the internal flags of that
2929
// AST node to see if the ActorIsolationChecker determined it crossed isolation.
3030
// It's possible this is brittle and a more nuanced check is needed, but this
3131
// suffices for all cases tested so far.
3232
static bool SILApplyCrossesIsolation(const SILInstruction *inst) {
33-
ApplyExpr *apply = inst->getLoc().getAsASTNode<ApplyExpr>();
33+
if (ApplyExpr *apply = inst->getLoc().getAsASTNode<ApplyExpr>())
34+
return apply->getIsolationCrossing().has_value();
35+
3436
// if the instruction doesn't correspond to an ApplyExpr, then it can't
3537
// cross an isolation domain
36-
if (!apply)
37-
return false;
38-
return apply->getIsolationCrossing().has_value();
38+
return false;
3939
}
4040

4141
inline bool isAddress(SILValue val) {
@@ -269,13 +269,16 @@ class PartitionOpTranslator {
269269
currentInstruction)};
270270
}
271271

272-
std::vector<PartitionOp> Consume(TrackableSILValue value) {
272+
std::vector<PartitionOp> Consume(
273+
TrackableSILValue value,
274+
Expr *sourceExpr = nullptr) {
273275
assert(valueHasID(value) &&
274276
"consumed value should already have been encountered");
275277

276278
return {PartitionOp::Consume(
277279
lookupValueID(value),
278-
currentInstruction)};
280+
currentInstruction,
281+
sourceExpr)};
279282
}
280283

281284
std::vector<PartitionOp> Merge(TrackableSILValue fst, TrackableSILValue snd) {
@@ -339,12 +342,10 @@ class PartitionOpTranslator {
339342
// the translations from source-level SILInstructions.
340343

341344
// require all non-sendable sources, merge their regions, and assign the
342-
// resulting region to all non-sendable targets. If consumeSrcs is true then
343-
// consume the source region instead and put all the targets in a single,
344-
// fresh region.
345+
// resulting region to all non-sendable targets, or assign non-sendable
346+
// targets to a fresh region if there are no non-sendable sources
345347
std::vector<PartitionOp> translateSILMultiAssign(
346-
std::vector<SILValue> tgts, std::vector<SILValue> srcs,
347-
bool consumeSrcs = false) {
348+
std::vector<SILValue> tgts, std::vector<SILValue> srcs) {
348349

349350
std::vector<TrackableSILValue> nonSendableSrcs;
350351
std::vector<TrackableSILValue> nonSendableTgts;
@@ -362,23 +363,6 @@ class PartitionOpTranslator {
362363
for (auto op : ops) translated.push_back(op);
363364
};
364365

365-
if (consumeSrcs) {
366-
for (auto src : nonSendableSrcs)
367-
add_to_translation(Consume(src));
368-
369-
// put all the tgts in a fresh region
370-
llvm::Optional<TrackableSILValue> fstTgt;
371-
for (auto tgt : nonSendableTgts)
372-
if (!fstTgt) {
373-
fstTgt = tgt;
374-
add_to_translation(AssignFresh(tgt));
375-
} else {
376-
add_to_translation(Assign(tgt, fstTgt.value()));
377-
}
378-
379-
return translated;
380-
}
381-
382366
// require all srcs
383367
for (auto src : nonSendableSrcs)
384368
add_to_translation(Require(src));
@@ -410,12 +394,69 @@ class PartitionOpTranslator {
410394
}
411395

412396
std::vector<PartitionOp> translateSILApply(const SILInstruction *applyInst) {
413-
return translateSILMultiAssign(
414-
{applyInst->getResult(0)},
415-
{applyInst->getOperandValues().begin(),
416-
applyInst->getOperandValues().end()},
417-
// consume operands iff the apply crosses isolation
418-
/*consumeSrcs=*/ SILApplyCrossesIsolation(applyInst));
397+
// if this apply does not cross isolation domains, it has normal,
398+
// non-consuming multi-assignment semantics
399+
if (!SILApplyCrossesIsolation(applyInst))
400+
return translateSILMultiAssign(
401+
{applyInst->getResult(0)},
402+
{applyInst->getOperandValues().begin(),
403+
applyInst->getOperandValues().end()}
404+
);
405+
406+
ApplyExpr *sourceApply = applyInst->getLoc().getAsASTNode<ApplyExpr>();
407+
assert(sourceApply && "only ApplyExpr's should cross isolation domains");
408+
409+
std::vector<PartitionOp> translated;
410+
auto getSourceArg = [&](unsigned i) {
411+
if (i < sourceApply->getArgs()->size())
412+
return sourceApply->getArgs()->getExpr(i);
413+
assert(false && "SIL instruction has too many arguments for"
414+
" corresponding AST node");
415+
return (Expr *)nullptr;
416+
};
417+
418+
auto getSourceSelf = [&]() {
419+
if (auto callExpr = dyn_cast<CallExpr>(sourceApply))
420+
if (auto calledExpr = dyn_cast<DotSyntaxCallExpr>(callExpr->getDirectCallee()))
421+
return calledExpr->getBase();
422+
return (Expr *)nullptr;
423+
};
424+
425+
auto handleSILOperands = [&](OperandValueArrayRef ops) {
426+
int argNum = 0;
427+
for (auto arg : ops) {
428+
if (auto trackArg = trackIfNonSendable(arg))
429+
translated.push_back(
430+
Consume(trackArg.value(), getSourceArg(argNum)).front());
431+
argNum++;
432+
}
433+
};
434+
435+
auto handleSILSelf = [&](SILValue self) {
436+
if (auto trackSelf = trackIfNonSendable(self))
437+
translated.push_back(
438+
Consume(trackSelf.value(), getSourceSelf()).front());
439+
};
440+
441+
if (auto applyInstCast = dyn_cast<ApplyInst>(applyInst)) {
442+
handleSILOperands(applyInstCast->getArgumentsWithoutSelf());
443+
if (applyInstCast->hasSelfArgument())
444+
handleSILSelf(applyInstCast->getSelfArgument());
445+
} else if (auto applyInstCase = dyn_cast<TryApplyInst>(applyInst)) {
446+
handleSILOperands(applyInstCast->getArgumentsWithoutSelf());
447+
if (applyInstCast->hasSelfArgument())
448+
handleSILSelf(applyInstCast->getSelfArgument());
449+
} else {
450+
llvm_unreachable("this instruction crossing isolation is not handled yet");
451+
}
452+
453+
// non-sendable results can't be returned from cross-isolation calls without
454+
// a diagnostic emitted elsewhere. Here, give them a fresh value for better
455+
// diagnostics hereafter
456+
if (auto trackResult = trackIfNonSendable(applyInst->getResult(0)))
457+
translated.push_back(AssignFresh(trackResult.value()).front());
458+
459+
return translated;
419460
}
420461

421462
std::vector<PartitionOp> translateSILAssign(SILValue tgt, SILValue src) {
@@ -1204,7 +1245,7 @@ class PartitionAnalysis {
12041245

12051246
// used for generating informative diagnostics
12061247
Expr *getExprForPartitionOp(const PartitionOp& op) {
1207-
SILInstruction *sourceInstr = op.getSourceInst(true);
1248+
SILInstruction *sourceInstr = op.getSourceInst(/*assertNonNull=*/true);
12081249
Expr *expr = sourceInstr->getLoc().getAsASTNode<Expr>();
12091250
assert(expr && "PartitionOp's source location should correspond to"
12101251
"an AST node");
@@ -1231,15 +1272,6 @@ class PartitionAnalysis {
12311272
if (hasBeenEmitted(expr)) return;
12321273

12331274
raceTracer.traceUseOfConsumedValue(partitionOp, consumedVal);
1234-
1235-
/*
1236-
* This handles diagnosing accesses to consumed values at the site
1237-
* of access instead of the site of consumption, as this is less
1238-
* useful it will likely be eliminated, but leaving it for now
1239-
1240-
function->getASTContext().Diags.diagnose(
1241-
expr->getLoc(), diag::consumed_value_used);
1242-
*/
12431275
},
12441276

12451277
/*handleConsumeNonConsumable=*/
@@ -1255,10 +1287,18 @@ class PartitionAnalysis {
12551287
/*diagnoseConsume=*/
12561288
[&](const PartitionOp& consumeOp,
12571289
unsigned numDisplayed, unsigned numHidden) {
1290+
1291+
if (tryDiagnoseAsCallSite(consumeOp, numDisplayed, numHidden))
1292+
return;
1293+
1294+
assert(false);
1295+
// default to more generic diagnostic
12581296
auto expr = getExprForPartitionOp(consumeOp);
1259-
function->getASTContext().Diags.diagnose(
1297+
auto diag = function->getASTContext().Diags.diagnose(
12601298
expr->getLoc(), diag::consumption_yields_race,
12611299
numDisplayed, numDisplayed != 1, numHidden > 0, numHidden);
1300+
if (auto sourceExpr = consumeOp.getSourceExpr())
1301+
diag.highlight(sourceExpr->getSourceRange());
12621302
},
12631303

12641304
/*diagnoseRequire=*/
@@ -1270,6 +1310,36 @@ class PartitionAnalysis {
12701310
});
12711311
}
12721312

1313+
// try to interpret this consumeOp as a source-level callsite (ApplyExpr),
1314+
// and report a diagnostic including actor isolation crossing information
1315+
// returns true iff one was succesfully formed and emitted
1316+
bool tryDiagnoseAsCallSite(
1317+
const PartitionOp& consumeOp, unsigned numDisplayed, unsigned numHidden) {
1318+
SILInstruction *sourceInst = consumeOp.getSourceInst(/*assertNonNull=*/true);
1319+
ApplyExpr *apply = sourceInst->getLoc().getAsASTNode<ApplyExpr>();
1320+
if (!apply)
1321+
// consumption does not correspond to an apply expression
1322+
return false;
1323+
auto isolationCrossing = apply->getIsolationCrossing();
1324+
if (!isolationCrossing) {
1325+
assert(false && "ApplyExprs should be consuming only if"
1326+
" they are isolation crossing");
1327+
return false;
1328+
}
1329+
auto argExpr = consumeOp.getSourceExpr();
1330+
if (!argExpr)
1331+
assert(false && "sourceExpr should be populated for ApplyExpr consumptions");
1332+
1333+
function->getASTContext().Diags.diagnose(
1334+
apply->getLoc(), diag::call_site_consumption_yields_race,
1335+
findOriginalValueType(argExpr),
1336+
isolationCrossing.value().getCallerIsolation(),
1337+
isolationCrossing.value().getCalleeIsolation(),
1338+
numDisplayed, numDisplayed != 1, numHidden > 0, numHidden)
1339+
.highlight(argExpr->getSourceRange());
1340+
return true;
1341+
}
1342+
12731343
public:
12741344

12751345
void dump() LLVM_ATTRIBUTE_USED {

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1813,7 +1813,7 @@ static void noteGlobalActorOnContext(DeclContext *dc, Type globalActor) {
18131813

18141814
/// Find the original type of a value, looking through various implicit
18151815
/// conversions.
1816-
static Type findOriginalValueType(Expr *expr) {
1816+
Type swift::findOriginalValueType(Expr *expr) {
18171817
do {
18181818
expr = expr->getSemanticsProvidingExpr();
18191819

lib/Sema/TypeCheckConcurrency.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,10 @@ struct SendableCheckContext {
362362
bool isExplicitSendableConformance() const;
363363
};
364364

365+
/// Find the original type of a value, looking through various implicit
366+
/// conversions.
367+
Type findOriginalValueType(Expr *expr);
368+
365369
/// Diagnose any non-Sendable types that occur within the given type, using
366370
/// the given diagnostic.
367371
///

0 commit comments

Comments
 (0)