Skip to content

Commit 9303c40

Browse files
committed
[region-isolation] Teach region isolation that assigning into a transferring parameter is a transfer of the value.
The specific semantics is if we assign into a transferring parameter's field, then we "merge" src's value into the transferring parameter, so we conservatively leave the region of the transferring parameter alone. If we assign over the entire transferring parameter, we perform an assign fresh since any value that used to be in the transferring parameter cannot reference anything in its new value since they are all gone.
1 parent 26a75fe commit 9303c40

File tree

6 files changed

+334
-27
lines changed

6 files changed

+334
-27
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,9 @@ WARNING(regionbasedisolation_transfer_yields_race_no_isolation, none,
898898
WARNING(regionbasedisolation_transfer_yields_race_with_isolation, none,
899899
"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",
900900
(Type, ActorIsolation, ActorIsolation))
901+
WARNING(regionbasedisolation_transfer_yields_race_transferring_parameter, none,
902+
"transferred value of non-Sendable type %0 into transferring parameter; later accesses could result in races",
903+
(Type))
901904
NOTE(regionbasedisolation_maybe_race, none,
902905
"access here could race", ())
903906
ERROR(regionbasedisolation_unknown_pattern, none,

include/swift/SIL/SILValue.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1655,6 +1655,15 @@ namespace llvm {
16551655
enum { NumLowBitsAvailable = swift::SILValue::NumLowBitsAvailable };
16561656
};
16571657

1658+
/// A SILValue can be checked if a value is present, so we can use it with
1659+
/// dyn_cast_or_null.
1660+
template <>
1661+
struct ValueIsPresent<swift::SILValue> {
1662+
using SILValue = swift::SILValue;
1663+
using UnwrappedType = SILValue;
1664+
static inline bool isPresent(const SILValue &t) { return bool(t); }
1665+
static inline decltype(auto) unwrapValue(SILValue &t) { return t; }
1666+
};
16581667
} // end namespace llvm
16591668

16601669
#endif

include/swift/SILOptimizer/Analysis/RegionAnalysis.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,10 @@ class regionanalysisimpl::TrackableValue {
254254

255255
TrackableValueState getValueState() const { return valueState; }
256256

257+
/// Returns true if this TrackableValue is an alloc_stack from a transferring
258+
/// parameter.
259+
bool isTransferringParameter() const;
260+
257261
void print(llvm::raw_ostream &os) const {
258262
os << "TrackableValue. State: ";
259263
valueState.print(os);

lib/SILOptimizer/Analysis/RegionAnalysis.cpp

Lines changed: 99 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,53 @@ static bool isTransferrableFunctionArgument(SILFunctionArgument *arg) {
432432
return arg->isTransferring();
433433
}
434434

435+
//===----------------------------------------------------------------------===//
436+
// MARK: TrackableValue
437+
//===----------------------------------------------------------------------===//
438+
439+
bool TrackableValue::isTransferringParameter() const {
440+
// First get our alloc_stack.
441+
//
442+
// TODO: We should just put a flag on the alloc_stack, so we /know/ 100% that
443+
// it is from a consuming parameter. We don't have that so we pattern match.
444+
auto *asi =
445+
dyn_cast_or_null<AllocStackInst>(representativeValue.maybeGetValue());
446+
if (!asi)
447+
return false;
448+
449+
if (asi->getParent() != asi->getFunction()->getEntryBlock())
450+
return false;
451+
452+
// See if we are initialized from a transferring parameter and are the only
453+
// use of the parameter.
454+
for (auto *use : asi->getUses()) {
455+
auto *user = use->getUser();
456+
457+
if (auto *si = dyn_cast<StoreInst>(user)) {
458+
// Check if our store inst is from a function argument that is
459+
// transferring. If not, then this isn't the consuming parameter
460+
// alloc_stack.
461+
auto *fArg = dyn_cast<SILFunctionArgument>(si->getSrc());
462+
if (!fArg || !fArg->isTransferring())
463+
return false;
464+
return fArg->getSingleUse();
465+
}
466+
467+
if (auto *copyAddr = dyn_cast<CopyAddrInst>(user)) {
468+
// Check if our store inst is from a function argument that is
469+
// transferring. If not, then this isn't the consuming parameter
470+
// alloc_stack.
471+
auto *fArg = dyn_cast<SILFunctionArgument>(copyAddr->getSrc());
472+
if (!fArg || !fArg->isTransferring())
473+
return false;
474+
return fArg->getSingleUse();
475+
}
476+
}
477+
478+
// Otherwise, this isn't a consuming parameter.
479+
return false;
480+
}
481+
435482
//===----------------------------------------------------------------------===//
436483
// MARK: Partial Apply Reachability
437484
//===----------------------------------------------------------------------===//
@@ -1701,13 +1748,52 @@ class PartitionOpTranslator {
17011748
return translateSILMerge(dest, TinyPtrVector<SILValue>(src));
17021749
}
17031750

1704-
/// If tgt is known to be unaliased (computed thropugh a combination of
1751+
void translateSILAssignmentToTransferringParameter(TrackableValue destRoot,
1752+
Operand *destOperand,
1753+
TrackableValue srcRoot,
1754+
Operand *srcOperand) {
1755+
assert(isa<AllocStackInst>(destRoot.getRepresentative().getValue()) &&
1756+
"Destination should always be an alloc_stack");
1757+
1758+
// Transfer src. This ensures that we cannot use src again locally in this
1759+
// function... which makes sense since its value is now in the transferring
1760+
// parameter.
1761+
builder.addTransfer(srcRoot.getRepresentative().getValue(), srcOperand);
1762+
1763+
// Then check if we are assigning into an aggregate projection. In such a
1764+
// case, we want to ensure that we keep tracking the elements already in the
1765+
// region of transferring. This is more conservative than we need to be
1766+
// (since we could forget anything reachable from the aggregate
1767+
// field)... but being more conservative is ok.
1768+
if (isProjectedFromAggregate(destOperand->get()))
1769+
return;
1770+
1771+
// If we are assigning over the entire value though, we perform an assign
1772+
// fresh since we are guaranteed that any value that could be referenced via
1773+
// the old value is gone.
1774+
builder.addAssignFresh(destRoot.getRepresentative().getValue());
1775+
}
1776+
1777+
/// If \p dest is known to be unaliased (computed through a combination of
17051778
/// AccessStorage's inUniquelyIdenfitied check and a custom search for
1706-
/// captures by applications), then these can be treated as assignments of tgt
1707-
/// to src. If the tgt could be aliased, then we must instead treat them as
1708-
/// merges, to ensure any aliases of tgt are also updated.
1709-
void translateSILStore(SILValue dest, SILValue src) {
1710-
if (auto nonSendableTgt = tryToTrackValue(dest)) {
1779+
/// captures by applications), then these can be treated as assignments of \p
1780+
/// dest to src. If the \p dest could be aliased, then we must instead treat
1781+
/// them as merges, to ensure any aliases of \p dest are also updated.
1782+
void translateSILStore(Operand *dest, Operand *src) {
1783+
SILValue destValue = dest->get();
1784+
SILValue srcValue = src->get();
1785+
1786+
if (auto nonSendableDest = tryToTrackValue(destValue)) {
1787+
// Before we do anything check if we have an assignment into an
1788+
// alloc_stack for a consuming transferring parameter... in such a case,
1789+
// we need to handle this specially.
1790+
if (nonSendableDest->isTransferringParameter()) {
1791+
if (auto nonSendableSrc = tryToTrackValue(srcValue)) {
1792+
return translateSILAssignmentToTransferringParameter(
1793+
*nonSendableDest, dest, *nonSendableSrc, src);
1794+
}
1795+
}
1796+
17111797
// In the following situations, we can perform an assign:
17121798
//
17131799
// 1. A store to unaliased storage.
@@ -1719,11 +1805,12 @@ class PartitionOpTranslator {
17191805
// specifically in this projection... but that is better than
17201806
// miscompiling. For memory like this, we probably need to track it on a
17211807
// per field basis to allow for us to assign.
1722-
if (nonSendableTgt.value().isNoAlias() && !isProjectedFromAggregate(dest))
1723-
return translateSILAssign(dest, src);
1808+
if (nonSendableDest.value().isNoAlias() &&
1809+
!isProjectedFromAggregate(destValue))
1810+
return translateSILAssign(destValue, srcValue);
17241811

17251812
// Stores to possibly aliased storage must be treated as merges.
1726-
return translateSILMerge(dest, src);
1813+
return translateSILMerge(destValue, srcValue);
17271814
}
17281815

17291816
// Stores to storage of non-Sendable type can be ignored.
@@ -1859,8 +1946,9 @@ class PartitionOpTranslator {
18591946
return translateSILLookThrough(inst->getResults(), inst->getOperand(0));
18601947

18611948
case TranslationSemantics::Store:
1862-
return translateSILStore(inst->getOperand(CopyLikeInstruction::Dest),
1863-
inst->getOperand(CopyLikeInstruction::Src));
1949+
return translateSILStore(
1950+
&inst->getAllOperands()[CopyLikeInstruction::Dest],
1951+
&inst->getAllOperands()[CopyLikeInstruction::Src]);
18641952

18651953
case TranslationSemantics::Special:
18661954
return;

0 commit comments

Comments
 (0)