Skip to content

Commit 828bf45

Browse files
committed
[rbi] Ensure that we error when two 'inout sending' parameters are in the same region at end of function.
The caller is allowed to assume that the 'inout sending' parameters are not in the same region on return so can be sent to different isolation domains safely. To enforce that we have to ensure on return that the two are /actually/ not in the same region. rdar://138519484
1 parent 81f1e75 commit 828bf45

File tree

9 files changed

+317
-7
lines changed

9 files changed

+317
-7
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,6 +1201,12 @@ NOTE(rbi_add_generic_parameter_sendable_conformance,none,
12011201
"consider making generic parameter %0 conform to the 'Sendable' protocol",
12021202
(Type))
12031203

1204+
ERROR(regionbasedisolation_inout_sending_in_same_region, none,
1205+
"'inout sending' parameters %0 and %1 can be potentially accessed from each other at function return risking data races in caller",
1206+
(Identifier, Identifier))
1207+
NOTE(regionbasedisolation_inout_sending_in_same_region_note, none,
1208+
"caller function assumes on return that %0 and %1 cannot be used to access each other implying sending them to different isolation domains does not risk a data race", (Identifier, Identifier))
1209+
12041210
// Concurrency related diagnostics
12051211
ERROR(cannot_find_executor_factory_type, none,
12061212
"the DefaultExecutorFactory type could not be found", ())

include/swift/SIL/SILArgument.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,9 @@ class SILFunctionArgument : public SILArgument {
426426

427427
bool isSending() const;
428428

429+
/// Returns true if this SILFunctionArgument is an 'inout sending' parameter.
430+
bool isInOutSending() const;
431+
429432
Lifetime getLifetime() const {
430433
return getType()
431434
.getLifetime(*getFunction())

include/swift/SILOptimizer/Utils/PartitionOpError.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,7 @@ PARTITION_OP_ERROR(UnknownCodePattern)
6666
/// non-Sendable value.
6767
PARTITION_OP_ERROR(NonSendableIsolationCrossingResult)
6868

69+
/// Used to signify that two 'inout sending' values are in the same region.
70+
PARTITION_OP_ERROR(InOutSendingParametersInSameRegion)
71+
6972
#undef PARTITION_OP_ERROR

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,29 @@ class PartitionOpError {
11911191
}
11921192
};
11931193

1194+
struct InOutSendingParametersInSameRegionError {
1195+
const PartitionOp *op;
1196+
Element firstInoutSendingParam;
1197+
SmallVector<Element, 1> otherInOutSendingParams;
1198+
1199+
InOutSendingParametersInSameRegionError(
1200+
const PartitionOp &op, Element firstInoutSendingParam,
1201+
SmallVector<Element, 1> &&otherInOutSendingParams)
1202+
: op(&op), firstInoutSendingParam(firstInoutSendingParam),
1203+
otherInOutSendingParams(std::move(otherInOutSendingParams)) {}
1204+
1205+
InOutSendingParametersInSameRegionError(
1206+
InOutSendingParametersInSameRegionError &&other) = default;
1207+
InOutSendingParametersInSameRegionError &
1208+
operator=(InOutSendingParametersInSameRegionError &&other) = default;
1209+
1210+
void print(llvm::raw_ostream &os, RegionAnalysisValueMap &valueMap) const;
1211+
1212+
SWIFT_DEBUG_DUMPER(dump(RegionAnalysisValueMap &valueMap)) {
1213+
print(llvm::dbgs(), valueMap);
1214+
}
1215+
};
1216+
11941217
struct NonSendableIsolationCrossingResultError {
11951218
const PartitionOp *op;
11961219

@@ -1408,6 +1431,42 @@ struct PartitionOpEvaluator {
14081431
return SILValue();
14091432
}
14101433

1434+
/// Given the region \p regionOfInOutSendingParam containing the 'inout
1435+
/// sending' parameter \p firstInOutSendingParam, see if that region contains
1436+
/// any other 'inout sending' parameters with greater element numbers than \p
1437+
/// firstInOutSendingParam. If so, place all of the found params into \p
1438+
/// foundInOutSendingParams and return true. Otherwise return false.
1439+
///
1440+
/// DISCUSSION: The reason why we return all of the found 'inout sending'
1441+
/// params is at this point we do not know if any of the params have
1442+
/// diagnostic behavior reduction. It would not be correct if we returned the
1443+
/// first one and that had that its diagnostic should be ignored but later
1444+
/// ones did not.
1445+
///
1446+
/// DISCUSSION: The reason why we only place in the elements that have element
1447+
/// numbers greater than \p firstInOutsendingParam is to ensure that we do not
1448+
/// emit diagnostics multiple times for the same variables, one for $VAR1,
1449+
/// $VAR2 and the second for $VAR2, $VAR1.
1450+
bool findOtherInOutSendingParameters(
1451+
Region regionOfInOutSendingParam, Element firstInOutSendingParam,
1452+
SmallVectorImpl<Element> &foundInOutSendingParams) const {
1453+
for (const auto &pair : p.range()) {
1454+
// Skip values that are not in the region or if the value is our
1455+
// firstInOutSendingParam.
1456+
if (pair.second != regionOfInOutSendingParam ||
1457+
pair.first <= firstInOutSendingParam)
1458+
continue;
1459+
1460+
auto *value = dyn_cast_or_null<SILFunctionArgument>(
1461+
getRepresentativeValue(pair.first).maybeGetValue());
1462+
if (!value || !value->isInOutSending())
1463+
continue;
1464+
foundInOutSendingParams.push_back(pair.first);
1465+
}
1466+
1467+
return foundInOutSendingParams.size();
1468+
}
1469+
14111470
bool isTaskIsolatedDerived(Element elt) const {
14121471
return asImpl().isTaskIsolatedDerived(elt);
14131472
}
@@ -1722,13 +1781,23 @@ struct PartitionOpEvaluator {
17221781
return;
17231782
}
17241783

1725-
// Ok, we have a disconnected value. We could still be returning a
1726-
// disconnected value in the same region as an 'inout sending'
1727-
// parameter. We cannot allow that since the caller considers 'inout
1728-
// sending' values to be in their own region on function return. So it
1729-
// would assume that it could potentially send the value in the 'inout
1730-
// sending' parameter and the return value to different isolation
1731-
// domains... allowing for races.
1784+
// Ok, we have a disconnected value. First check if we have two 'inout
1785+
// sending' values in the same region. We want the two values to still be
1786+
// in different regions in the caller.
1787+
SmallVector<Element, 1> foundInOutSendingElts;
1788+
if (findOtherInOutSendingParameters(inoutSendingRegion, op.getOpArg1(),
1789+
foundInOutSendingElts)) {
1790+
handleError(InOutSendingParametersInSameRegionError(
1791+
op, op.getOpArg1(), std::move(foundInOutSendingElts)));
1792+
return;
1793+
}
1794+
1795+
// Finally We could still be returning a disconnected value in the same
1796+
// region as an 'inout sending' parameter. We cannot allow that since the
1797+
// caller considers 'inout sending' values to be in their own region on
1798+
// function return. So it would assume that it could potentially send the
1799+
// value in the 'inout sending' parameter and the return value to
1800+
// different isolation domains... allowing for races.
17321801

17331802
// Even though we are disconnected, see if our SILFunction has an actor
17341803
// isolation. In that case, we want to use that since the direct return

lib/SIL/IR/SILArgument.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,3 +442,10 @@ bool SILFunctionArgument::isSending() const {
442442
return getFunction()->getLoweredFunctionType()->hasSendingResult();
443443
return getKnownParameterInfo().hasOption(SILParameterInfo::Sending);
444444
}
445+
446+
bool SILFunctionArgument::isInOutSending() const {
447+
// Make sure that we are sending, not an indirect result (since indirect
448+
// results can be sending) and have an inout convention.
449+
return isSending() && !isIndirectResult() &&
450+
getArgumentConvention().isInoutConvention();
451+
}

lib/SILOptimizer/Mandatory/SendNonSendable.cpp

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,7 @@ struct DiagnosticEvaluator final
723723
case PartitionOpError::SentNeverSendable:
724724
case PartitionOpError::AssignNeverSendableIntoSendingResult:
725725
case PartitionOpError::NonSendableIsolationCrossingResult:
726+
case PartitionOpError::InOutSendingParametersInSameRegion:
726727
// We are going to process these later... but dump so we can see that we
727728
// handled an error here. The rest of the explicit handlers will dump as
728729
// appropriate if they want to emit an error here (some will squelch the
@@ -3501,6 +3502,138 @@ void NonSendableIsolationCrossingResultDiagnosticEmitter::emit() {
35013502
}
35023503
}
35033504

3505+
//===----------------------------------------------------------------------===//
3506+
// MARK: InOutSendingParametersInSameRegionError
3507+
//===----------------------------------------------------------------------===//
3508+
3509+
namespace {
3510+
3511+
class InOutSendingParametersInSameRegionDiagnosticEmitter {
3512+
/// The function exiting inst where the 'inout sending' parameter was actor
3513+
/// isolated.
3514+
TermInst *functionExitingInst;
3515+
3516+
/// The first 'inout sending' param in the region.
3517+
SILValue firstInOutSendingParam;
3518+
3519+
/// The second 'inout sending' param in the region.
3520+
SILValue secondInOutSendingParam;
3521+
3522+
bool emittedErrorDiagnostic = false;
3523+
3524+
public:
3525+
InOutSendingParametersInSameRegionDiagnosticEmitter(
3526+
TermInst *functionExitingInst, SILValue firstInOutSendingParam,
3527+
SILValue secondInOutSendingParam)
3528+
: functionExitingInst(functionExitingInst),
3529+
firstInOutSendingParam(firstInOutSendingParam),
3530+
secondInOutSendingParam(secondInOutSendingParam) {}
3531+
3532+
~InOutSendingParametersInSameRegionDiagnosticEmitter() {
3533+
// If we were supposed to emit a diagnostic and didn't emit an unknown
3534+
// pattern error.
3535+
if (!emittedErrorDiagnostic)
3536+
emitUnknownPatternError();
3537+
}
3538+
3539+
SILFunction *getFunction() const {
3540+
return functionExitingInst->getFunction();
3541+
}
3542+
3543+
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
3544+
auto first =
3545+
firstInOutSendingParam->getType().getConcurrencyDiagnosticBehavior(
3546+
getFunction());
3547+
auto second =
3548+
secondInOutSendingParam->getType().getConcurrencyDiagnosticBehavior(
3549+
getFunction());
3550+
if (!first)
3551+
return second;
3552+
if (!second)
3553+
return first;
3554+
return first->merge(*second);
3555+
}
3556+
3557+
void emitUnknownPatternError() {
3558+
if (shouldAbortOnUnknownPatternMatchError()) {
3559+
llvm::report_fatal_error(
3560+
"RegionIsolation: Aborting on unknown pattern match error");
3561+
}
3562+
3563+
diagnoseError(functionExitingInst,
3564+
diag::regionbasedisolation_unknown_pattern)
3565+
.limitBehaviorIf(getBehaviorLimit());
3566+
}
3567+
3568+
void emit();
3569+
3570+
ASTContext &getASTContext() const {
3571+
return functionExitingInst->getFunction()->getASTContext();
3572+
}
3573+
3574+
template <typename... T, typename... U>
3575+
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
3576+
U &&...args) {
3577+
emittedErrorDiagnostic = true;
3578+
return std::move(getASTContext()
3579+
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
3580+
.warnUntilSwiftVersion(6));
3581+
}
3582+
3583+
template <typename... T, typename... U>
3584+
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
3585+
U &&...args) {
3586+
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
3587+
}
3588+
3589+
template <typename... T, typename... U>
3590+
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
3591+
U &&...args) {
3592+
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
3593+
}
3594+
3595+
template <typename... T, typename... U>
3596+
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
3597+
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
3598+
}
3599+
3600+
template <typename... T, typename... U>
3601+
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
3602+
U &&...args) {
3603+
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
3604+
}
3605+
3606+
template <typename... T, typename... U>
3607+
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
3608+
U &&...args) {
3609+
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
3610+
}
3611+
};
3612+
3613+
} // namespace
3614+
3615+
void InOutSendingParametersInSameRegionDiagnosticEmitter::emit() {
3616+
// We should always be able to find a name for an inout sending param. If we
3617+
// do not, emit an unknown pattern error.
3618+
auto firstName = inferNameHelper(firstInOutSendingParam);
3619+
if (!firstName) {
3620+
return emitUnknownPatternError();
3621+
}
3622+
auto secondName = inferNameHelper(secondInOutSendingParam);
3623+
if (!secondName) {
3624+
return emitUnknownPatternError();
3625+
}
3626+
3627+
diagnoseError(functionExitingInst,
3628+
diag::regionbasedisolation_inout_sending_in_same_region,
3629+
*firstName, *secondName)
3630+
.limitBehaviorIf(getBehaviorLimit());
3631+
3632+
diagnoseNote(functionExitingInst,
3633+
diag::regionbasedisolation_inout_sending_in_same_region_note,
3634+
*firstName, *secondName);
3635+
}
3636+
35043637
//===----------------------------------------------------------------------===//
35053638
// MARK: Top Level Entrypoint
35063639
//===----------------------------------------------------------------------===//
@@ -3569,6 +3702,30 @@ void SendNonSendableImpl::emitVerbatimErrors() {
35693702
diagnosticInferrer.emit();
35703703
continue;
35713704
}
3705+
case PartitionOpError::InOutSendingParametersInSameRegion: {
3706+
auto e =
3707+
std::move(erasedError).getInOutSendingParametersInSameRegionError();
3708+
REGIONBASEDISOLATION_LOG(e.print(llvm::dbgs(), info->getValueMap()));
3709+
auto firstParam =
3710+
info->getValueMap().getRepresentativeValue(e.firstInoutSendingParam);
3711+
// Walk through our secondInOutSendingParams and use one that is not
3712+
// ignore.
3713+
for (auto paramElt : e.otherInOutSendingParams) {
3714+
auto paramValue =
3715+
info->getValueMap().getRepresentativeValue(paramElt).getValue();
3716+
if (auto behavior =
3717+
paramValue->getType().getConcurrencyDiagnosticBehavior(
3718+
info->getFunction());
3719+
behavior && *behavior == DiagnosticBehavior::Ignore) {
3720+
continue;
3721+
}
3722+
InOutSendingParametersInSameRegionDiagnosticEmitter diagnosticEmitter(
3723+
cast<TermInst>(e.op->getSourceInst()), firstParam.getValue(),
3724+
paramValue);
3725+
diagnosticEmitter.emit();
3726+
}
3727+
continue;
3728+
}
35723729
}
35733730
llvm_unreachable("Covered switch isn't covered?!");
35743731
}

lib/SILOptimizer/Utils/PartitionUtils.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,18 @@ void PartitionOpError::InOutSendingReturnedError::print(
111111
<< " Rep: " << valueMap.getRepresentativeValue(returnedValue);
112112
}
113113

114+
void PartitionOpError::InOutSendingParametersInSameRegionError::print(
115+
llvm::raw_ostream &os, RegionAnalysisValueMap &valueMap) const {
116+
os << " Emitting Error. Kind: InOutSendingParametersInSameRegion!\n"
117+
<< " First ID: %%" << firstInoutSendingParam
118+
<< " First Rep: "
119+
<< valueMap.getRepresentativeValue(firstInoutSendingParam);
120+
for (auto other : otherInOutSendingParams) {
121+
os << " Other ID: %%" << other
122+
<< " Other Rep: " << valueMap.getRepresentativeValue(other);
123+
}
124+
}
125+
114126
//===----------------------------------------------------------------------===//
115127
// MARK: PartitionOp
116128
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)