Skip to content

Commit 6d20c74

Browse files
authored
Merge pull request #83903 from gottesmm/rdar152454571
[rbi] Teach RegionIsolation how to properly error when 'inout sending' params are returned.
2 parents 5464129 + 8745ab0 commit 6d20c74

File tree

11 files changed

+3535
-207
lines changed

11 files changed

+3535
-207
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,32 @@ ERROR(regionbasedisolation_inout_sending_cannot_be_actor_isolated, none,
11191119
NOTE(regionbasedisolation_inout_sending_cannot_be_actor_isolated_note, none,
11201120
"%1 %0 risks causing races in between %1 uses and caller uses since caller assumes value is not actor isolated",
11211121
(Identifier, StringRef))
1122+
ERROR(regionbasedisolation_inout_sending_cannot_be_returned_param, none,
1123+
"'inout sending' parameter %0 cannot be returned",
1124+
(Identifier))
1125+
ERROR(regionbasedisolation_inout_sending_cannot_be_returned_value, none,
1126+
"%0 cannot be returned",
1127+
(Identifier))
1128+
ERROR(regionbasedisolation_inout_sending_cannot_be_returned_value_result, none,
1129+
"result of %kind0 cannot be returned",
1130+
(const ValueDecl *))
1131+
NOTE(regionbasedisolation_inout_sending_cannot_be_returned_note_param, none,
1132+
"returning 'inout sending' parameter %0 risks concurrent access as caller assumes %0 and result can be sent to different isolation domains",
1133+
(Identifier))
1134+
NOTE(regionbasedisolation_inout_sending_cannot_be_returned_note_value, none,
1135+
"returning %0 risks concurrent access to 'inout sending' parameter %1 as caller assumes %1 and result can be sent to different isolation domains",
1136+
(Identifier, Identifier))
1137+
NOTE(regionbasedisolation_inout_sending_cannot_be_returned_note_return_value, none,
1138+
"returning result of %kind0 risks concurrent access to 'inout sending' parameter %1 as caller assumes %1 and result can be sent to different isolation domains",
1139+
(const ValueDecl *, Identifier))
1140+
NOTE(regionbasedisolation_inout_sending_cannot_be_returned_note_actor_param, none,
1141+
"returning %0 risks concurrent access as caller assumes %0 is not actor-isolated and result is %1", (Identifier, StringRef))
1142+
NOTE(regionbasedisolation_inout_sending_cannot_be_returned_note_actor_value, none,
1143+
"returning %0 risks concurrent access to 'inout sending' parameter %1 as caller assumes %1 is not actor-isolated and result is %2",
1144+
(Identifier, Identifier, StringRef))
1145+
NOTE(regionbasedisolation_inout_sending_cannot_be_returned_note_actor_return_value, none,
1146+
"returning result of %kind0 risks concurrent access to 'inout sending' parameter %1 as caller assumes %1 is not actor-isolated and result is %2",
1147+
(const ValueDecl *, Identifier, StringRef))
11221148

11231149
//===
11241150
// Out Sending

include/swift/SILOptimizer/Analysis/RegionAnalysis.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,10 @@ class RegionAnalysisFunctionInfo {
544544

545545
bool isSupportedFunction() const { return supportedFunction; }
546546

547+
NullablePtr<BlockPartitionState> getBlockState(SILBasicBlock *block) const {
548+
return blockStates->get(block);
549+
}
550+
547551
using iterator = BasicBlockData::iterator;
548552
using const_iterator = BasicBlockData::const_iterator;
549553
using reverse_iterator = BasicBlockData::reverse_iterator;

include/swift/SILOptimizer/Utils/PartitionOpError.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ PARTITION_OP_ERROR(InOutSendingNotInitializedAtExit)
5252
/// at end of function without being reinitialized with something disconnected.
5353
PARTITION_OP_ERROR(InOutSendingNotDisconnectedAtExit)
5454

55+
/// This is emitted when a concrete inout sending parameter is returned.
56+
PARTITION_OP_ERROR(InOutSendingReturned)
57+
5558
/// Used to signify an "unknown code pattern" has occured while performing
5659
/// dataflow.
5760
///

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 154 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,40 @@ class PartitionOpError {
11261126
}
11271127
};
11281128

1129+
struct InOutSendingReturnedError {
1130+
const PartitionOp *op;
1131+
1132+
/// The 'inout sending' parameter that caused the error to be emitted.
1133+
Element inoutSendingElement;
1134+
1135+
/// The actual returned value. If we return the 'inout sending' parameter
1136+
/// then this will equal inoutSendingElement.
1137+
Element returnedValue;
1138+
1139+
/// If we are actor isolated due to being in the same region as the out
1140+
/// parameter of a actor isolated method... this is that actor isolation.
1141+
SILDynamicMergedIsolationInfo isolationInfo;
1142+
1143+
InOutSendingReturnedError(const PartitionOp &op,
1144+
Element inoutSendingElement,
1145+
Element returnedValue,
1146+
SILDynamicMergedIsolationInfo isolationInfo = {})
1147+
: op(&op), inoutSendingElement(inoutSendingElement),
1148+
returnedValue(returnedValue), isolationInfo(isolationInfo) {}
1149+
1150+
InOutSendingReturnedError(const PartitionOp &op,
1151+
Element inoutSendingElement,
1152+
SILDynamicMergedIsolationInfo isolationInfo = {})
1153+
: InOutSendingReturnedError(op, inoutSendingElement,
1154+
inoutSendingElement, isolationInfo) {}
1155+
1156+
void print(llvm::raw_ostream &os, RegionAnalysisValueMap &valueMap) const;
1157+
1158+
SWIFT_DEBUG_DUMPER(dump(RegionAnalysisValueMap &valueMap)) {
1159+
print(llvm::dbgs(), valueMap);
1160+
}
1161+
};
1162+
11291163
struct NonSendableIsolationCrossingResultError {
11301164
const PartitionOp *op;
11311165

@@ -1297,6 +1331,42 @@ struct PartitionOpEvaluator {
12971331
return SILDynamicMergedIsolationInfo();
12981332
}
12991333

1334+
/// If \p region is not disconnected only because of an out parameter, return
1335+
/// that out parameter. We do not care about other non-disconnected parameters
1336+
/// since we always want to prefer the return error.
1337+
SILValue findNonDisconnectedOutParameterInRegion(Region region) const {
1338+
for (const auto &pair : p.range()) {
1339+
if (pair.second != region)
1340+
continue;
1341+
auto info = getIsolationRegionInfo(pair.first);
1342+
1343+
// If we do not have any isolation info, then something bad
1344+
// happened. Return SILValue().
1345+
if (!info)
1346+
return {};
1347+
1348+
// If we have something that is disconnected... continue. We do not care
1349+
// about this.
1350+
if (info.isDisconnected())
1351+
continue;
1352+
1353+
// See if we ahve an indirect out parameter...
1354+
if (auto value = asImpl().getIndirectOutParameter(pair.first)) {
1355+
return value;
1356+
}
1357+
1358+
// If this was not an out parameter, return {}.
1359+
return {};
1360+
}
1361+
1362+
// After going over our loop, if result is not SILValue(), then we found
1363+
// that our region is not disconnected due only to a single 'indirect out'
1364+
// parameter. If SILValue() then we had all disconnected values. If we found
1365+
// multiple non-disconnected things, we would have bailed earlier in the
1366+
// loop itself.
1367+
return SILValue();
1368+
}
1369+
13001370
bool isTaskIsolatedDerived(Element elt) const {
13011371
return asImpl().isTaskIsolatedDerived(elt);
13021372
}
@@ -1530,7 +1600,9 @@ struct PartitionOpEvaluator {
15301600
"Require PartitionOp's argument should already be tracked");
15311601

15321602
// First check if the region of our 'inout sending' element has been
1533-
// sent. In that case, we emit a special use after free error.
1603+
// sent. In that case, we emit a special use after free error so that the
1604+
// user knows that they need to reinitialize the 'inout sending'
1605+
// parameter.
15341606
if (auto *sentOperandSet = p.getSentOperandSet(op.getOpArg1())) {
15351607
for (auto sentOperand : sentOperandSet->data()) {
15361608
handleError(InOutSendingNotInitializedAtExitError(op, op.getOpArg1(),
@@ -1539,7 +1611,7 @@ struct PartitionOpEvaluator {
15391611
return;
15401612
}
15411613

1542-
// If we were not sent, check if our region is actor isolated. If so,
1614+
// If we were not sent, check if our region is not disconnected. If so,
15431615
// error since we need a disconnected value in the inout parameter.
15441616
Region inoutSendingRegion = p.getRegion(op.getOpArg1());
15451617
auto dynamicRegionIsolation = getIsolationRegionInfo(inoutSendingRegion);
@@ -1550,12 +1622,82 @@ struct PartitionOpEvaluator {
15501622
return;
15511623
}
15521624

1553-
// Otherwise, emit the error if the dynamic region isolation is not
1554-
// disconnected.
1625+
auto handleDirectReturn = [&]() -> bool {
1626+
bool emittedDiagnostic = false;
1627+
1628+
// For each value we are returning...
1629+
for (auto value : op.getSourceInst()->getOperandValues()) {
1630+
// Find its element and check if that element is in the same region as
1631+
// our inout sending param...
1632+
auto elt = asImpl().getElement(value);
1633+
if (!elt || inoutSendingRegion != p.getRegion(*elt))
1634+
continue;
1635+
1636+
emittedDiagnostic = true;
1637+
// Then check if our element is the same as our inoutSendingParam. In
1638+
// that case, we emit a special error that only refers to the
1639+
// inoutSendingParam as one entity.
1640+
//
1641+
// NOTE: This is taking advantage of us looking through loads when
1642+
// determining element identity. When that changes, this code will
1643+
// need to be updated to look through loads.
1644+
if (*elt == op.getOpArg1()) {
1645+
handleError(InOutSendingReturnedError(op, op.getOpArg1(),
1646+
dynamicRegionIsolation));
1647+
continue;
1648+
}
1649+
1650+
// Otherwise, we need to refer to a different value in the same region
1651+
// as the 'inout sending' parameter. Emit a special error. For that.
1652+
handleError(InOutSendingReturnedError(op, op.getOpArg1(), *elt,
1653+
dynamicRegionIsolation));
1654+
}
1655+
1656+
return emittedDiagnostic;
1657+
};
1658+
1659+
// Then check if our region isolated value is not disconnected...
15551660
if (!dynamicRegionIsolation.isDisconnected()) {
1661+
// Before we emit the more general "inout sending" not disconnected at
1662+
// exit error... check if our dynamic region isolation is not
1663+
// disconnected since it is in the same region an out parameter. In that
1664+
// case we want to emit a special 'cannot return value' error.
1665+
if (auto outParam =
1666+
findNonDisconnectedOutParameterInRegion(inoutSendingRegion)) {
1667+
handleError(InOutSendingReturnedError(op, op.getOpArg1(),
1668+
getElement(outParam).value(),
1669+
dynamicRegionIsolation));
1670+
return;
1671+
}
1672+
1673+
// If we did not have an out parameter, see if we are returning a
1674+
// value that is in the region as our 'inout sending' parameter.
1675+
if (handleDirectReturn())
1676+
return;
1677+
1678+
// Otherwise, we emit the normal not disconnected at exit error.
15561679
handleError(InOutSendingNotDisconnectedAtExitError(
15571680
op, op.getOpArg1(), dynamicRegionIsolation));
1681+
return;
15581682
}
1683+
1684+
// Ok, we have a disconnected value. We could still be returning a
1685+
// disconnected value in the same region as an 'inout sending'
1686+
// parameter. We cannot allow that since the caller considers 'inout
1687+
// sending' values to be in their own region on function return. So it
1688+
// would assume that it could potentially send the value in the 'inout
1689+
// sending' parameter and the return value to different isolation
1690+
// domains... allowing for races.
1691+
1692+
// Even though we are disconnected, see if our SILFunction has an actor
1693+
// isolation. In that case, we want to use that since the direct return
1694+
// value will be inferred in the caller to have that isolation.
1695+
if (auto isolation = SILIsolationInfo::getFunctionIsolation(
1696+
op.getSourceInst()->getFunction())) {
1697+
dynamicRegionIsolation = {isolation};
1698+
}
1699+
1700+
handleDirectReturn();
15591701
return;
15601702
}
15611703
case PartitionOpKind::UnknownPatternError:
@@ -1565,10 +1707,6 @@ struct PartitionOpEvaluator {
15651707
// Then emit an unknown code pattern error.
15661708
return handleError(UnknownCodePatternError(op));
15671709
case PartitionOpKind::NonSendableIsolationCrossingResult: {
1568-
// Grab the dynamic dataflow isolation information for our element's
1569-
// region.
1570-
Region region = p.getRegion(op.getOpArg1());
1571-
15721710
// Then emit the error.
15731711
return handleError(
15741712
NonSendableIsolationCrossingResultError(op, op.getOpArg1()));
@@ -1753,6 +1891,14 @@ struct PartitionOpEvaluatorBaseImpl : PartitionOpEvaluator<Subclass> {
17531891
/// isolated value.
17541892
bool isTaskIsolatedDerived(Element elt) const { return false; }
17551893

1894+
/// If \p elt maps to a representative that is an indirect out parameter,
1895+
/// return that value.
1896+
SILValue getIndirectOutParameter(Element elt) const { return {}; }
1897+
1898+
/// If \p elt maps to a representative that is an 'inout sending' parameter
1899+
/// that returns that value.
1900+
SILValue getInOutSendingParameter(Element elt) const { return {}; }
1901+
17561902
/// By default, do nothing upon error.
17571903
void handleError(PartitionOpError error) {}
17581904

include/swift/SILOptimizer/Utils/SILIsolationInfo.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,11 @@ class SILIsolationInfo {
546546
/// Infer isolation of conformances for the given instruction.
547547
static SILIsolationInfo getConformanceIsolation(SILInstruction *inst);
548548

549+
/// Return SILIsolationInfo based off of the attached ActorIsolation of \p
550+
/// fn. If \p fn does not have an actor isolation set, returns an invalid
551+
/// SILIsolationInfo.
552+
static SILIsolationInfo getFunctionIsolation(SILFunction *fn);
553+
549554
/// A helper that is used to ensure that we treat certain builtin values as
550555
/// non-Sendable that the AST level otherwise thinks are non-Sendable.
551556
///

lib/SILOptimizer/Analysis/RegionAnalysis.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4115,7 +4115,10 @@ bool BlockPartitionState::recomputeExitFromEntry(
41154115
}
41164116

41174117
std::optional<Element> getElement(SILValue value) const {
4118-
return translator.getValueMap().getTrackableValue(value).value.getID();
4118+
auto trackableValue = translator.getValueMap().getTrackableValue(value);
4119+
if (trackableValue.value.isSendable())
4120+
return {};
4121+
return trackableValue.value.getID();
41194122
}
41204123

41214124
SILValue getRepresentative(SILValue value) const {

0 commit comments

Comments
 (0)