Skip to content

Commit 12573d6

Browse files
committed
[region-isolation] Instead of just tracking a single transferring instruction, track all of them.
Previously I avoided doing this since the only problem would be that in a case where we had two transfer instructions that were in an if-else block, we would just emit an error for one: ```swift if boolValue { transfer(x) } else { transfer(x) // Only emit error for this transfer! } useValue(x) ``` Now that we are tracking at the transfer point if any element in the transfer was captured in a closure, this becomes an actual semantic issue since if we track the transfer instruction that isn't reachable from the closure capture, we will not become more pessimistic: ```swift if boolValue { closure = { useInOut(&x) } transfer(x) } else { transfer(x) } // Since we grab from the else block, sendableField is allowed to be accessed // since we do not track that x was captured by reference in a closure. x.sendableField useValue(x) ``` To be truly safe, we need to emit both errors. rdar://119048779
1 parent 150efb6 commit 12573d6

File tree

5 files changed

+306
-98
lines changed

5 files changed

+306
-98
lines changed

include/swift/Basic/ImmutablePointerSet.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ class ImmutablePointerSet : public llvm::FoldingSetNode {
119119
using iterator = typename llvm::ArrayRef<T>::iterator;
120120
iterator begin() const { return Data.begin(); }
121121
iterator end() const { return Data.end(); }
122+
llvm::iterator_range<iterator> range() const { return {begin(), end()}; }
123+
llvm::ArrayRef<T> data() const { return Data; }
122124

123125
unsigned size() const { return Data.size(); }
124126
bool empty() const { return Data.empty(); }

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 176 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include "swift/Basic/Defer.h"
1717
#include "swift/Basic/FrozenMultiMap.h"
18+
#include "swift/Basic/ImmutablePointerSet.h"
1819
#include "swift/Basic/LLVM.h"
1920
#include "swift/SIL/SILInstruction.h"
2021
#include "llvm/ADT/SmallVector.h"
@@ -105,6 +106,34 @@ struct TransferringOperand {
105106
bool isClosureCaptured() const { return value.getInt(); }
106107

107108
SILInstruction *getUser() const { return getOperand()->getUser(); }
109+
110+
bool operator<(const TransferringOperand &other) const {
111+
return value < other.value;
112+
}
113+
114+
bool operator>=(const TransferringOperand &other) const {
115+
return !(value < other.value);
116+
}
117+
118+
bool operator>(const TransferringOperand &other) const {
119+
return value > other.value;
120+
}
121+
122+
bool operator<=(const TransferringOperand &other) const {
123+
return !(value > other.value);
124+
}
125+
126+
bool operator==(const TransferringOperand &other) const {
127+
return value == other.value;
128+
}
129+
130+
void print(llvm::raw_ostream &os) const {
131+
os << "Op Num: " << getOperand()->getOperandNumber() << ". "
132+
<< "Capture: " << (isClosureCaptured() ? "yes. " : "no. ")
133+
<< "User: " << *getUser();
134+
}
135+
136+
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
108137
};
109138

110139
} // namespace swift
@@ -344,8 +373,22 @@ class Partition {
344373

345374
using Element = PartitionPrimitives::Element;
346375
using Region = PartitionPrimitives::Region;
376+
using TransferringOperandSet = ImmutablePointerSet<TransferringOperand>;
377+
using TransferringOperandSetFactory =
378+
ImmutablePointerSetFactory<TransferringOperand>;
347379

348380
private:
381+
/// A map from a region number to a instruction that consumes it.
382+
///
383+
/// All we care is that we ever track a single SILInstruction for a region
384+
/// since we are fine with emitting a single error per value and letting the
385+
/// user recompile. If this is an ask for in the future, we can use a true
386+
/// multi map here. The implication of this is that when we are performing
387+
/// dataflow we use a union operation to combine CFG elements and just take
388+
/// the first instruction that we see.
389+
llvm::SmallDenseMap<Region, TransferringOperandSet *, 2>
390+
regionToTransferredOpMap;
391+
349392
/// Label each index with a non-negative (unsigned) label if it is associated
350393
/// with a valid region.
351394
std::map<Element, Region> elementToRegionMap;
@@ -360,16 +403,6 @@ class Partition {
360403
/// must be reestablished by a call to canonicalize().
361404
bool canonical;
362405

363-
/// A map from a region number to a instruction that consumes it.
364-
///
365-
/// All we care is that we ever track a single SILInstruction for a region
366-
/// since we are fine with emitting a single error per value and letting the
367-
/// user recompile. If this is an ask for in the future, we can use a true
368-
/// multi map here. The implication of this is that when we are performing
369-
/// dataflow we use a union operation to combine CFG elements and just take
370-
/// the first instruction that we see.
371-
llvm::SmallDenseMap<Region, TransferringOperand, 2> regionToTransferredOpMap;
372-
373406
public:
374407
Partition() : elementToRegionMap({}), canonical(true) {}
375408

@@ -420,25 +453,32 @@ class Partition {
420453

421454
bool isTracked(Element val) const { return elementToRegionMap.count(val); }
422455

423-
/// Mark val as transferred. Returns true if we inserted \p
424-
/// transferOperand. We return false otherwise.
425-
bool markTransferred(Element val, TransferringOperand transferredOperand) {
456+
/// Mark val as transferred.
457+
void markTransferred(Element val,
458+
TransferringOperandSet *transferredOperandSet) {
426459
// First see if our val is tracked. If it is not tracked, insert it and mark
427460
// its new region as transferred.
428461
if (!isTracked(val)) {
429462
elementToRegionMap.insert_or_assign(val, fresh_label);
430-
regionToTransferredOpMap.insert({fresh_label, transferredOperand});
463+
regionToTransferredOpMap.insert({fresh_label, transferredOperandSet});
431464
fresh_label = Region(fresh_label + 1);
432465
canonical = false;
433-
return true;
466+
return;
434467
}
435468

436469
// Otherwise, we already have this value in the map. Try to insert it.
437470
auto iter1 = elementToRegionMap.find(val);
438471
assert(iter1 != elementToRegionMap.end());
439-
auto iter2 =
440-
regionToTransferredOpMap.try_emplace(iter1->second, transferredOperand);
441-
return iter2.second;
472+
auto iter2 = regionToTransferredOpMap.try_emplace(iter1->second,
473+
transferredOperandSet);
474+
475+
// If we did insert, just return. We were not tracking any state.
476+
if (iter2.second)
477+
return;
478+
479+
// Otherwise, we need to merge the sets.
480+
iter2.first->getSecond() =
481+
iter2.first->second->merge(transferredOperandSet);
442482
}
443483

444484
/// If val was marked as transferred, unmark it as transfer. Returns true if
@@ -481,8 +521,10 @@ class Partition {
481521
sndReduced.canonicalize();
482522

483523
// For each (sndEltNumber, sndRegionNumber) in snd_reduced...
484-
for (const auto &[sndEltNumber, sndRegionNumber] :
485-
sndReduced.elementToRegionMap) {
524+
for (auto pair : sndReduced.elementToRegionMap) {
525+
auto sndEltNumber = pair.first;
526+
auto sndRegionNumber = pair.second;
527+
486528
// Check if fstReduced has sndEltNumber within it...
487529
if (fstReduced.elementToRegionMap.count(sndEltNumber)) {
488530
// If we do, we just merge sndEltNumber into fstRegion.
@@ -491,10 +533,15 @@ class Partition {
491533

492534
// Then if sndRegionNumber is transferred in sndReduced, make sure
493535
// mergedRegion is transferred in fstReduced.
494-
auto iter = sndReduced.regionToTransferredOpMap.find(sndRegionNumber);
495-
if (iter != sndReduced.regionToTransferredOpMap.end()) {
496-
fstReduced.regionToTransferredOpMap.try_emplace(mergedRegion,
497-
iter->second);
536+
auto sndIter =
537+
sndReduced.regionToTransferredOpMap.find(sndRegionNumber);
538+
if (sndIter != sndReduced.regionToTransferredOpMap.end()) {
539+
auto fstIter = fstReduced.regionToTransferredOpMap.try_emplace(
540+
mergedRegion, sndIter->second);
541+
if (!fstIter.second) {
542+
fstIter.first->getSecond() =
543+
fstIter.first->getSecond()->merge(sndIter->second);
544+
}
498545
}
499546
continue;
500547
}
@@ -527,10 +574,13 @@ class Partition {
527574
// due to our traversal being in order. Thus just add this to fst_reduced.
528575
assert(sndEltNumber == Element(sndRegionNumber));
529576
fstReduced.elementToRegionMap.insert({sndEltNumber, sndRegionNumber});
530-
auto iter = sndReduced.regionToTransferredOpMap.find(sndRegionNumber);
531-
if (iter != sndReduced.regionToTransferredOpMap.end()) {
532-
fstReduced.regionToTransferredOpMap.insert(
533-
{sndRegionNumber, iter->second});
577+
auto sndIter = sndReduced.regionToTransferredOpMap.find(sndRegionNumber);
578+
if (sndIter != sndReduced.regionToTransferredOpMap.end()) {
579+
auto fstIter = fstReduced.regionToTransferredOpMap.try_emplace(
580+
sndRegionNumber, sndIter->second);
581+
if (!fstIter.second)
582+
fstIter.first->getSecond() =
583+
fstIter.first->second->merge(sndIter->second);
534584
}
535585
if (fstReduced.fresh_label < sndRegionNumber)
536586
fstReduced.fresh_label = Region(sndEltNumber + 1);
@@ -598,8 +648,13 @@ class Partition {
598648
for (auto [regionNo, elementNumbers] : multimap.getRange()) {
599649
auto iter = regionToTransferredOpMap.find(regionNo);
600650
bool isTransferred = iter != regionToTransferredOpMap.end();
601-
bool isClosureCaptured =
602-
isTransferred ? iter->getSecond().isClosureCaptured() : false;
651+
bool isClosureCaptured = false;
652+
if (isTransferred) {
653+
isClosureCaptured = llvm::any_of(
654+
iter->getSecond()->range(), [](const TransferringOperand &operand) {
655+
return operand.isClosureCaptured();
656+
});
657+
}
603658

604659
if (isTransferred) {
605660
os << '{';
@@ -624,6 +679,60 @@ class Partition {
624679
os << "]\n";
625680
}
626681

682+
LLVM_ATTRIBUTE_USED void dumpVerbose() const { printVerbose(llvm::dbgs()); }
683+
684+
void printVerbose(llvm::raw_ostream &os) const {
685+
SmallFrozenMultiMap<Region, Element, 8> multimap;
686+
687+
for (auto [eltNo, regionNo] : elementToRegionMap)
688+
multimap.insert(regionNo, eltNo);
689+
690+
multimap.setFrozen();
691+
692+
for (auto [regionNo, elementNumbers] : multimap.getRange()) {
693+
auto iter = regionToTransferredOpMap.find(regionNo);
694+
bool isTransferred = iter != regionToTransferredOpMap.end();
695+
bool isClosureCaptured = false;
696+
if (isTransferred) {
697+
isClosureCaptured = llvm::any_of(
698+
iter->getSecond()->range(), [](const TransferringOperand &operand) {
699+
return operand.isClosureCaptured();
700+
});
701+
}
702+
703+
os << "Region: " << regionNo << ". ";
704+
if (isTransferred) {
705+
os << '{';
706+
if (isClosureCaptured)
707+
os << '*';
708+
} else {
709+
os << '(';
710+
}
711+
712+
int j = 0;
713+
for (Element i : elementNumbers) {
714+
os << (j++ ? " " : "") << i;
715+
}
716+
if (isTransferred) {
717+
if (isClosureCaptured)
718+
os << '*';
719+
os << '}';
720+
} else {
721+
os << ')';
722+
}
723+
os << "\n";
724+
os << "TransferInsts:\n";
725+
if (isTransferred) {
726+
for (auto op : iter->getSecond()->data()) {
727+
os << " ";
728+
op.print(os);
729+
}
730+
} else {
731+
os << "None.\n";
732+
}
733+
}
734+
}
735+
627736
bool isTransferred(Element val) const {
628737
auto iter = elementToRegionMap.find(val);
629738
if (iter == elementToRegionMap.end())
@@ -633,14 +742,16 @@ class Partition {
633742

634743
/// Return the instruction that transferred \p val's region or nullptr
635744
/// otherwise.
636-
std::optional<TransferringOperand> getTransferred(Element val) const {
745+
TransferringOperandSet *getTransferred(Element val) const {
637746
auto iter = elementToRegionMap.find(val);
638747
if (iter == elementToRegionMap.end())
639-
return std::nullopt;
748+
return nullptr;
640749
auto iter2 = regionToTransferredOpMap.find(iter->second);
641750
if (iter2 == regionToTransferredOpMap.end())
642-
return std::nullopt;
643-
return iter2->second;
751+
return nullptr;
752+
auto *set = iter2->second;
753+
assert(!set->empty());
754+
return set;
644755
}
645756

646757
private:
@@ -723,7 +834,7 @@ class Partition {
723834
//
724835
// TODO: If we just used an array for this, we could just rewrite and
725836
// re-sort and not have to deal with potential allocations.
726-
llvm::SmallDenseMap<Region, TransferringOperand, 2> oldMap =
837+
decltype(regionToTransferredOpMap) oldMap =
727838
std::move(regionToTransferredOpMap);
728839
for (auto &[oldReg, op] : oldMap) {
729840
auto iter = oldRegionToRelabeledMap.find(oldReg);
@@ -816,6 +927,10 @@ class Partition {
816927
struct PartitionOpEvaluator {
817928
using Element = PartitionPrimitives::Element;
818929
using Region = PartitionPrimitives::Region;
930+
using TransferringOperandSetFactory =
931+
Partition::TransferringOperandSetFactory;
932+
933+
TransferringOperandSetFactory &ptrSetFactory;
819934

820935
Partition &p;
821936

@@ -864,7 +979,9 @@ struct PartitionOpEvaluator {
864979
std::function<bool(Element elt, Operand *op)> isClosureCapturedCallback =
865980
nullptr;
866981

867-
PartitionOpEvaluator(Partition &p) : p(p) {}
982+
PartitionOpEvaluator(Partition &p,
983+
TransferringOperandSetFactory &ptrSetFactory)
984+
: ptrSetFactory(ptrSetFactory), p(p) {}
868985

869986
/// A wrapper around the failure callback that checks if it is nullptr.
870987
void handleFailure(const PartitionOp &op, Element elt,
@@ -922,8 +1039,11 @@ struct PartitionOpEvaluator {
9221039
"Assign PartitionOp's source argument should be already tracked");
9231040
// If we are using a region that was transferred as our assignment source
9241041
// value... emit an error.
925-
if (auto transferredOperand = p.getTransferred(op.getOpArgs()[1]))
926-
handleFailure(op, op.getOpArgs()[1], *transferredOperand);
1042+
if (auto *transferredOperandSet = p.getTransferred(op.getOpArgs()[1])) {
1043+
for (auto transferredOperand : transferredOperandSet->data()) {
1044+
handleFailure(op, op.getOpArgs()[1], transferredOperand);
1045+
}
1046+
}
9271047

9281048
p.elementToRegionMap.insert_or_assign(
9291049
op.getOpArgs()[0], p.elementToRegionMap.at(op.getOpArgs()[1]));
@@ -978,8 +1098,9 @@ struct PartitionOpEvaluator {
9781098
}
9791099

9801100
// Mark op.getOpArgs()[0] as transferred.
981-
p.markTransferred(op.getOpArgs()[0],
982-
{op.getSourceOp(), isClosureCapturedElt});
1101+
p.markTransferred(
1102+
op.getOpArgs()[0],
1103+
ptrSetFactory.get({op.getSourceOp(), isClosureCapturedElt}));
9831104
return;
9841105
}
9851106
case PartitionOpKind::UndoTransfer: {
@@ -1000,10 +1121,16 @@ struct PartitionOpEvaluator {
10001121
"Merge PartitionOp's arguments should already be tracked");
10011122

10021123
// if attempting to merge a transferred region, handle the failure
1003-
if (auto transferringOp = p.getTransferred(op.getOpArgs()[0]))
1004-
handleFailure(op, op.getOpArgs()[0], *transferringOp);
1005-
if (auto transferringOp = p.getTransferred(op.getOpArgs()[1]))
1006-
handleFailure(op, op.getOpArgs()[1], *transferringOp);
1124+
if (auto *transferredOperandSet = p.getTransferred(op.getOpArgs()[0])) {
1125+
for (auto transferredOperand : transferredOperandSet->data()) {
1126+
handleFailure(op, op.getOpArgs()[0], transferredOperand);
1127+
}
1128+
}
1129+
if (auto *transferredOperandSet = p.getTransferred(op.getOpArgs()[1])) {
1130+
for (auto transferredOperand : transferredOperandSet->data()) {
1131+
handleFailure(op, op.getOpArgs()[1], transferredOperand);
1132+
}
1133+
}
10071134

10081135
p.merge(op.getOpArgs()[0], op.getOpArgs()[1]);
10091136
return;
@@ -1012,8 +1139,11 @@ struct PartitionOpEvaluator {
10121139
"Require PartitionOp should be passed 1 argument");
10131140
assert(p.elementToRegionMap.count(op.getOpArgs()[0]) &&
10141141
"Require PartitionOp's argument should already be tracked");
1015-
if (auto transferringOp = p.getTransferred(op.getOpArgs()[0]))
1016-
handleFailure(op, op.getOpArgs()[0], *transferringOp);
1142+
if (auto *transferredOperandSet = p.getTransferred(op.getOpArgs()[0])) {
1143+
for (auto transferredOperand : transferredOperandSet->data()) {
1144+
handleFailure(op, op.getOpArgs()[0], transferredOperand);
1145+
}
1146+
}
10171147
return;
10181148
}
10191149

0 commit comments

Comments
 (0)