Skip to content

Commit 6ab8b56

Browse files
authored
Merge pull request #84879 from gottesmm/pr-9c51dc994a742cf2eaf0f068c64f5e3ed57a22f9
[rbi] Ensure that we error when two 'inout sending' parameters are in the same region at end of function.
2 parents a9d409d + 828bf45 commit 6ab8b56

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)