Skip to content

Commit edb5d6a

Browse files
authored
Merge pull request #69619 from gottesmm/rdar117273952
[region-isolation] Fix semantics around assigning into projections
2 parents ed25ad3 + 345b4cc commit edb5d6a

File tree

5 files changed

+1013
-529
lines changed

5 files changed

+1013
-529
lines changed

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 214 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ struct Region {
6565

6666
static Region transferred() { return Region(-1); }
6767
};
68-
}
68+
69+
} // namespace PartitionPrimitives
6970

7071
using namespace PartitionPrimitives;
7172

@@ -180,24 +181,44 @@ class PartitionOp {
180181

181182
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
182183

183-
void print(llvm::raw_ostream &os) const {
184+
void print(llvm::raw_ostream &os, bool extraSpace = false) const {
184185
switch (OpKind) {
185-
case PartitionOpKind::Assign:
186-
os << "assign %%" << OpArgs[0] << " = %%" << OpArgs[1];
186+
case PartitionOpKind::Assign: {
187+
constexpr static char extraSpaceLiteral[10] = " ";
188+
os << "assign ";
189+
if (extraSpace)
190+
os << extraSpaceLiteral;
191+
os << "%%" << OpArgs[0] << " = %%" << OpArgs[1];
187192
break;
193+
}
188194
case PartitionOpKind::AssignFresh:
189195
os << "assign_fresh %%" << OpArgs[0];
190196
break;
191-
case PartitionOpKind::Transfer:
192-
os << "transfer %%" << OpArgs[0];
197+
case PartitionOpKind::Transfer: {
198+
constexpr static char extraSpaceLiteral[10] = " ";
199+
os << "transfer ";
200+
if (extraSpace)
201+
os << extraSpaceLiteral;
202+
os << "%%" << OpArgs[0];
193203
break;
194-
case PartitionOpKind::Merge:
195-
os << "merge %%" << OpArgs[0] << " with %%" << OpArgs[1];
204+
}
205+
case PartitionOpKind::Merge: {
206+
constexpr static char extraSpaceLiteral[10] = " ";
207+
os << "merge ";
208+
if (extraSpace)
209+
os << extraSpaceLiteral;
210+
os << "%%" << OpArgs[0] << " with %%" << OpArgs[1];
196211
break;
197-
case PartitionOpKind::Require:
198-
os << "require %%" << OpArgs[0];
212+
}
213+
case PartitionOpKind::Require: {
214+
constexpr static char extraSpaceLiteral[10] = " ";
215+
os << "require ";
216+
if (extraSpace)
217+
os << extraSpaceLiteral;
218+
os << "%%" << OpArgs[0];
199219
break;
200220
}
221+
}
201222
os << ": " << *getSourceInst(true);
202223
}
203224
};
@@ -224,6 +245,8 @@ static void horizontalUpdate(std::map<Element, Region> &map, Element key,
224245
map.insert_or_assign(otherKey, val);
225246
}
226247

248+
struct PartitionOpEvaluator;
249+
227250
/// A map from Element -> Region that represents the current partition set.
228251
///
229252
///
@@ -232,6 +255,7 @@ class Partition {
232255
/// A class defined in PartitionUtils unittest used to grab state from
233256
/// Partition without exposing it to other users.
234257
struct PartitionTester;
258+
friend PartitionOpEvaluator;
235259

236260
private:
237261
/// Label each index with a non-negative (unsigned) label if it is associated
@@ -377,125 +401,6 @@ class Partition {
377401
return fst_reduced;
378402
}
379403

380-
/// Apply the passed PartitionOp to this partition, performing its action. A
381-
/// `handleFailure` closure can optionally be passed in that will be called if
382-
/// a transferred region is required. The closure is given the PartitionOp
383-
/// that failed, and the index of the SIL value that was required but
384-
/// transferred. Additionally, a list of "nontransferrable" indices can be
385-
/// passed in along with a handleTransferNonTransferrable closure. In the
386-
/// event that a region containing one of the nontransferrable indices is
387-
/// transferred, the closure will be called with the offending transfer.
388-
void apply(
389-
PartitionOp op,
390-
llvm::function_ref<void(const PartitionOp &, Element)> handleFailure =
391-
[](const PartitionOp &, Element) {},
392-
ArrayRef<Element> nontransferrables = {},
393-
llvm::function_ref<void(const PartitionOp &, Element)>
394-
handleTransferNonTransferrable = [](const PartitionOp &, Element) {},
395-
llvm::function_ref<bool(Element)> isActorDerived = nullptr) {
396-
397-
REGIONBASEDISOLATION_VERBOSE_LOG(llvm::dbgs() << "Applying: ";
398-
op.print(llvm::dbgs()));
399-
REGIONBASEDISOLATION_VERBOSE_LOG(llvm::dbgs() << " Before: ";
400-
print(llvm::dbgs()));
401-
SWIFT_DEFER {
402-
REGIONBASEDISOLATION_VERBOSE_LOG(llvm::dbgs() << " After: ";
403-
print(llvm::dbgs()));
404-
};
405-
switch (op.OpKind) {
406-
case PartitionOpKind::Assign:
407-
assert(op.OpArgs.size() == 2 &&
408-
"Assign PartitionOp should be passed 2 arguments");
409-
assert(labels.count(op.OpArgs[1]) &&
410-
"Assign PartitionOp's source argument should be already tracked");
411-
// if assigning to a missing region, handle the failure
412-
if (isTransferred(op.OpArgs[1]))
413-
handleFailure(op, op.OpArgs[1]);
414-
415-
labels.insert_or_assign(op.OpArgs[0], labels.at(op.OpArgs[1]));
416-
417-
// assignment could have invalidated canonicality of either the old region
418-
// of op.OpArgs[0] or the region of op.OpArgs[1], or both
419-
canonical = false;
420-
break;
421-
case PartitionOpKind::AssignFresh:
422-
assert(op.OpArgs.size() == 1 &&
423-
"AssignFresh PartitionOp should be passed 1 argument");
424-
425-
// map index op.OpArgs[0] to a fresh label
426-
labels.insert_or_assign(op.OpArgs[0], fresh_label);
427-
428-
// increment the fresh label so it remains fresh
429-
fresh_label = Region(fresh_label + 1);
430-
canonical = false;
431-
break;
432-
case PartitionOpKind::Transfer: {
433-
assert(op.OpArgs.size() == 1 &&
434-
"Transfer PartitionOp should be passed 1 argument");
435-
assert(labels.count(op.OpArgs[0]) &&
436-
"Transfer PartitionOp's argument should already be tracked");
437-
438-
// check if any nontransferrables are transferred here, and handle the
439-
// failure if so
440-
for (Element nonTransferrable : nontransferrables) {
441-
assert(
442-
labels.count(nonTransferrable) &&
443-
"nontransferrables should be function args and self, and therefore"
444-
"always present in the label map because of initialization at "
445-
"entry");
446-
if (!isTransferred(nonTransferrable) &&
447-
labels.at(nonTransferrable) == labels.at(op.OpArgs[0])) {
448-
handleTransferNonTransferrable(op, nonTransferrable);
449-
break;
450-
}
451-
}
452-
453-
// If this value is actor derived or if any elements in its region are
454-
// actor derived, we need to treat as nontransferrable.
455-
if (isActorDerived && isActorDerived(op.OpArgs[0]))
456-
return handleTransferNonTransferrable(op, op.OpArgs[0]);
457-
Region elementRegion = labels.at(op.OpArgs[0]);
458-
if (llvm::any_of(labels,
459-
[&](const std::pair<Element, Region> &pair) -> bool {
460-
if (pair.second != elementRegion)
461-
return false;
462-
return isActorDerived && isActorDerived(pair.first);
463-
}))
464-
return handleTransferNonTransferrable(op, op.OpArgs[0]);
465-
466-
// Ensure if the region is transferred...
467-
if (!isTransferred(op.OpArgs[0]))
468-
// that all elements associated with the region are marked as
469-
// transferred.
470-
horizontalUpdate(labels, op.OpArgs[0], Region::transferred());
471-
break;
472-
}
473-
case PartitionOpKind::Merge:
474-
assert(op.OpArgs.size() == 2 &&
475-
"Merge PartitionOp should be passed 2 arguments");
476-
assert(labels.count(op.OpArgs[0]) && labels.count(op.OpArgs[1]) &&
477-
"Merge PartitionOp's arguments should already be tracked");
478-
479-
// if attempting to merge a transferred region, handle the failure
480-
if (isTransferred(op.OpArgs[0]))
481-
handleFailure(op, op.OpArgs[0]);
482-
if (isTransferred(op.OpArgs[1]))
483-
handleFailure(op, op.OpArgs[1]);
484-
485-
merge(op.OpArgs[0], op.OpArgs[1]);
486-
break;
487-
case PartitionOpKind::Require:
488-
assert(op.OpArgs.size() == 1 &&
489-
"Require PartitionOp should be passed 1 argument");
490-
assert(labels.count(op.OpArgs[0]) &&
491-
"Require PartitionOp's argument should already be tracked");
492-
if (isTransferred(op.OpArgs[0]))
493-
handleFailure(op, op.OpArgs[0]);
494-
}
495-
496-
assert(is_canonical_correct());
497-
}
498-
499404
/// Return a vector of the transferred values in this partition.
500405
std::vector<Element> getTransferredVals() const {
501406
// For effeciency, this could return an iterator not a vector.
@@ -645,6 +550,186 @@ class Partition {
645550
}
646551
};
647552

553+
/// A data structure that applies a series of PartitionOps to a single Partition
554+
/// that it modifies.
555+
///
556+
/// Apply the passed PartitionOp to this partition, performing its action. A
557+
/// `handleFailure` closure can optionally be passed in that will be called if
558+
/// a transferred region is required. The closure is given the PartitionOp
559+
/// that failed, and the index of the SIL value that was required but
560+
/// transferred. Additionally, a list of "nontransferrable" indices can be
561+
/// passed in along with a handleTransferNonTransferrable closure. In the
562+
/// event that a region containing one of the nontransferrable indices is
563+
/// transferred, the closure will be called with the offending transfer.
564+
struct PartitionOpEvaluator {
565+
Partition &p;
566+
567+
/// If this PartitionOp evaluator should emit log statements.
568+
bool emitLog = true;
569+
570+
/// If set to a non-null function, then this callback will be called if we
571+
/// discover a transferred value was used after it was transferred.
572+
std::function<void(const PartitionOp &, Element)> failureCallback = nullptr;
573+
574+
/// A list of elements that cannot be transferred. Whenever we transfer, we
575+
/// check this list to see if we are transferring the element and then call
576+
/// transferNonTransferrableCallback. This should consist only of function
577+
/// arguments.
578+
ArrayRef<Element> nonTransferrableElements = {};
579+
580+
/// If set to a non-null function_ref, this is called if we detect a never
581+
/// transferred element that was passed to a transfer instruction.
582+
std::function<void(const PartitionOp &, Element)>
583+
transferredNonTransferrableCallback = nullptr;
584+
585+
/// If set to a non-null function_ref, then this is used to determine if an
586+
/// element is actor derived. If we determine that a region containing such an
587+
/// element is transferred, we emit an error since actor regions cannot be
588+
/// transferred.
589+
std::function<bool(Element)> isActorDerivedCallback = nullptr;
590+
591+
PartitionOpEvaluator(Partition &p) : p(p) {}
592+
593+
/// A wrapper around the failure callback that checks if it is nullptr.
594+
void handleFailure(const PartitionOp &op, Element elt) const {
595+
if (!failureCallback)
596+
return;
597+
failureCallback(op, elt);
598+
}
599+
600+
/// A wrapper around transferNonTransferrableCallback that only calls it if it
601+
/// is not null.
602+
void handleTransferNonTransferrable(const PartitionOp &op,
603+
Element elt) const {
604+
if (!transferredNonTransferrableCallback)
605+
return;
606+
transferredNonTransferrableCallback(op, elt);
607+
}
608+
609+
/// A wrapper around isActorDerivedCallback that returns false if
610+
/// isActorDerivedCallback is nullptr and otherwise returns
611+
/// isActorDerivedCallback().
612+
bool isActorDerived(Element elt) const {
613+
return bool(isActorDerivedCallback) && isActorDerivedCallback(elt);
614+
}
615+
616+
/// Apply \p op to the partition op.
617+
void apply(PartitionOp op) {
618+
if (emitLog) {
619+
REGIONBASEDISOLATION_VERBOSE_LOG(llvm::dbgs() << "Applying: ";
620+
op.print(llvm::dbgs()));
621+
REGIONBASEDISOLATION_VERBOSE_LOG(llvm::dbgs() << " Before: ";
622+
p.print(llvm::dbgs()));
623+
}
624+
SWIFT_DEFER {
625+
if (emitLog) {
626+
REGIONBASEDISOLATION_VERBOSE_LOG(llvm::dbgs() << " After: ";
627+
p.print(llvm::dbgs()));
628+
}
629+
};
630+
631+
switch (op.getKind()) {
632+
case PartitionOpKind::Assign:
633+
assert(op.getOpArgs().size() == 2 &&
634+
"Assign PartitionOp should be passed 2 arguments");
635+
assert(p.labels.count(op.getOpArgs()[1]) &&
636+
"Assign PartitionOp's source argument should be already tracked");
637+
// if assigning to a missing region, handle the failure
638+
if (p.isTransferred(op.getOpArgs()[1]))
639+
handleFailure(op, op.getOpArgs()[1]);
640+
641+
p.labels.insert_or_assign(op.getOpArgs()[0],
642+
p.labels.at(op.getOpArgs()[1]));
643+
644+
// assignment could have invalidated canonicality of either the old region
645+
// of op.getOpArgs()[0] or the region of op.getOpArgs()[1], or both
646+
p.canonical = false;
647+
break;
648+
case PartitionOpKind::AssignFresh:
649+
assert(op.getOpArgs().size() == 1 &&
650+
"AssignFresh PartitionOp should be passed 1 argument");
651+
652+
// map index op.getOpArgs()[0] to a fresh label
653+
p.labels.insert_or_assign(op.getOpArgs()[0], p.fresh_label);
654+
655+
// increment the fresh label so it remains fresh
656+
p.fresh_label = Region(p.fresh_label + 1);
657+
p.canonical = false;
658+
break;
659+
case PartitionOpKind::Transfer: {
660+
assert(op.getOpArgs().size() == 1 &&
661+
"Transfer PartitionOp should be passed 1 argument");
662+
assert(p.labels.count(op.getOpArgs()[0]) &&
663+
"Transfer PartitionOp's argument should already be tracked");
664+
665+
// check if any nontransferrables are transferred here, and handle the
666+
// failure if so
667+
for (Element nonTransferrable : nonTransferrableElements) {
668+
assert(
669+
p.labels.count(nonTransferrable) &&
670+
"nontransferrables should be function args and self, and therefore"
671+
"always present in the label map because of initialization at "
672+
"entry");
673+
if (!p.isTransferred(nonTransferrable) &&
674+
p.labels.at(nonTransferrable) == p.labels.at(op.getOpArgs()[0])) {
675+
handleTransferNonTransferrable(op, nonTransferrable);
676+
break;
677+
}
678+
}
679+
680+
// If this value is actor derived or if any elements in its region are
681+
// actor derived, we need to treat as nontransferrable.
682+
if (isActorDerived(op.getOpArgs()[0]))
683+
return handleTransferNonTransferrable(op, op.getOpArgs()[0]);
684+
Region elementRegion = p.labels.at(op.getOpArgs()[0]);
685+
if (llvm::any_of(p.labels,
686+
[&](const std::pair<Element, Region> &pair) -> bool {
687+
if (pair.second != elementRegion)
688+
return false;
689+
return isActorDerived(pair.first);
690+
}))
691+
return handleTransferNonTransferrable(op, op.getOpArgs()[0]);
692+
693+
// Ensure if the region is transferred...
694+
if (!p.isTransferred(op.getOpArgs()[0]))
695+
// that all elements associated with the region are marked as
696+
// transferred.
697+
horizontalUpdate(p.labels, op.getOpArgs()[0], Region::transferred());
698+
break;
699+
}
700+
case PartitionOpKind::Merge:
701+
assert(op.getOpArgs().size() == 2 &&
702+
"Merge PartitionOp should be passed 2 arguments");
703+
assert(p.labels.count(op.getOpArgs()[0]) &&
704+
p.labels.count(op.getOpArgs()[1]) &&
705+
"Merge PartitionOp's arguments should already be tracked");
706+
707+
// if attempting to merge a transferred region, handle the failure
708+
if (p.isTransferred(op.getOpArgs()[0]))
709+
handleFailure(op, op.getOpArgs()[0]);
710+
if (p.isTransferred(op.getOpArgs()[1]))
711+
handleFailure(op, op.getOpArgs()[1]);
712+
713+
p.merge(op.getOpArgs()[0], op.getOpArgs()[1]);
714+
break;
715+
case PartitionOpKind::Require:
716+
assert(op.getOpArgs().size() == 1 &&
717+
"Require PartitionOp should be passed 1 argument");
718+
assert(p.labels.count(op.getOpArgs()[0]) &&
719+
"Require PartitionOp's argument should already be tracked");
720+
if (p.isTransferred(op.getOpArgs()[0]))
721+
handleFailure(op, op.getOpArgs()[0]);
722+
}
723+
724+
assert(p.is_canonical_correct());
725+
}
726+
727+
void apply(std::initializer_list<PartitionOp> ops) {
728+
for (auto &o : ops)
729+
apply(o);
730+
}
731+
};
732+
648733
} // namespace swift
649734

650735
#endif // SWIFT_PARTITIONUTILS_H

0 commit comments

Comments
 (0)