Skip to content

Commit dbc3deb

Browse files
committed
[region-isolation] Improve logging.
Specifically: 1. I changed Partition::apply so that it has an emitLog flag. The reason why I did this is we run apply in a few different situations sometimes when we want to emit logging other times when we really don't. For instance, we want to emit logging when walking instructions and updating the entry partition. On the other hand, we do not want to emit logging if we apply a value to a partition while attempting to determine why an error needed to be emitted. 2. When we create an assign partition op and we see that our destination and source are the same representative, we do not create the actual assign. Before we did not log this so it looked like there was a logic error that was stopping us from emitting a partition op when visiting said instructions. Now, we emit a small logging message so it isn't possible to be confused. 3. Since I am adding another parameter to Partition::apply, I decided to refactor Partition::apply to be in a separate PartitionOpEvaluator data structure that contains the options that we used to pass into Partition::apply. This prevents any mistakes around configuring Partition::apply since the fields provide nice names/common sense default values.
1 parent 888c66a commit dbc3deb

File tree

3 files changed

+634
-408
lines changed

3 files changed

+634
-408
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)